From 6a296b0b0959777fe5e0043401f420ac99094240 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 11 Dec 2024 03:56:28 +0100 Subject: [PATCH 01/57] chore: migrate dogsled --- pkg/golinters/dogsled/dogsled.go | 104 +++++++++++-------------------- 1 file changed, 36 insertions(+), 68 deletions(-) diff --git a/pkg/golinters/dogsled/dogsled.go b/pkg/golinters/dogsled/dogsled.go index 49108f4f1fcc..afa8152facbd 100644 --- a/pkg/golinters/dogsled/dogsled.go +++ b/pkg/golinters/dogsled/dogsled.go @@ -1,41 +1,26 @@ package dogsled import ( - "fmt" "go/ast" - "go/token" - "sync" "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "dogsled" func New(settings *config.DogsledSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues := runDogsled(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil + return run(pass, settings.MaxBlankIdentifiers) }, + Requires: []*analysis.Analyzer{inspect.Analyzer}, } return goanalysis.NewLinter( @@ -43,68 +28,51 @@ func New(settings *config.DogsledSettings) *goanalysis.Linter { "Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runDogsled(pass *analysis.Pass, settings *config.DogsledSettings) []goanalysis.Issue { - var reports []goanalysis.Issue - for _, f := range pass.Files { - v := &returnsVisitor{ - maxBlanks: settings.MaxBlankIdentifiers, - f: pass.Fset, - } - - ast.Walk(v, f) - - for i := range v.issues { - reports = append(reports, goanalysis.NewIssue(&v.issues[i], pass)) - } - } - - return reports -} - -type returnsVisitor struct { - f *token.FileSet - maxBlanks int - issues []result.Issue -} - -func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor { - funcDecl, ok := node.(*ast.FuncDecl) +func run(pass *analysis.Pass, maxBlanks int) (any, error) { + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) if !ok { - return v + return nil, nil } - if funcDecl.Body == nil { - return v + + nodeFilter := []ast.Node{ + (*ast.FuncDecl)(nil), } - for _, expr := range funcDecl.Body.List { - assgnStmt, ok := expr.(*ast.AssignStmt) + insp.Preorder(nodeFilter, func(node ast.Node) { + funcDecl, ok := node.(*ast.FuncDecl) if !ok { - continue + return + } + + if funcDecl.Body == nil { + return } - numBlank := 0 - for _, left := range assgnStmt.Lhs { - ident, ok := left.(*ast.Ident) + for _, expr := range funcDecl.Body.List { + assgnStmt, ok := expr.(*ast.AssignStmt) if !ok { continue } - if ident.Name == "_" { - numBlank++ + + numBlank := 0 + for _, left := range assgnStmt.Lhs { + ident, ok := left.(*ast.Ident) + if !ok { + continue + } + if ident.Name == "_" { + numBlank++ + } } - } - if numBlank > v.maxBlanks { - v.issues = append(v.issues, result.Issue{ - FromLinter: linterName, - Text: fmt.Sprintf("declaration has %v blank identifiers", numBlank), - Pos: v.f.Position(assgnStmt.Pos()), - }) + if numBlank > maxBlanks { + pass.Reportf(assgnStmt.Pos(), "declaration has %v blank identifiers", numBlank) + } } - } - return v + }) + + return nil, nil } From b73ec12a9949cc1a3ddaeea449f4e2e12ca100bf Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 11 Dec 2024 04:10:51 +0100 Subject: [PATCH 02/57] chore: migrate gochecknoinits --- .../gochecknoinits/gochecknoinits.go | 64 +++++++------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/pkg/golinters/gochecknoinits/gochecknoinits.go b/pkg/golinters/gochecknoinits/gochecknoinits.go index 1345eb8c29f9..510a06c91dd2 100644 --- a/pkg/golinters/gochecknoinits/gochecknoinits.go +++ b/pkg/golinters/gochecknoinits/gochecknoinits.go @@ -1,46 +1,24 @@ package gochecknoinits import ( - "fmt" "go/ast" - "go/token" - "sync" "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" "github.com/golangci/golangci-lint/pkg/goanalysis" "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "gochecknoinits" func New() *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (any, error) { - var res []goanalysis.Issue - for _, file := range pass.Files { - fileIssues := checkFileForInits(file, pass.Fset) - for i := range fileIssues { - res = append(res, goanalysis.NewIssue(&fileIssues[i], pass)) - } - } - if len(res) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, res...) - mu.Unlock() - - return nil, nil - }, + Name: linterName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, } return goanalysis.NewLinter( @@ -48,28 +26,30 @@ func New() *goanalysis.Linter { "Checks that no init functions are present in Go code", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func checkFileForInits(f *ast.File, fset *token.FileSet) []result.Issue { - var res []result.Issue - for _, decl := range f.Decls { +func run(pass *analysis.Pass) (any, error) { + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, nil + } + + nodeFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + } + + insp.Preorder(nodeFilter, func(decl ast.Node) { funcDecl, ok := decl.(*ast.FuncDecl) if !ok { - continue + return } fnName := funcDecl.Name.Name if fnName == "init" && funcDecl.Recv.NumFields() == 0 { - res = append(res, result.Issue{ - Pos: fset.Position(funcDecl.Pos()), - Text: fmt.Sprintf("don't use %s function", internal.FormatCode(fnName, nil)), - FromLinter: linterName, - }) + pass.Reportf(funcDecl.Pos(), "don't use %s function", internal.FormatCode(fnName, nil)) } - } + }) - return res + return nil, nil } From 08d2476840324d88fc1b9a67d53dd7491df0503e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:42:42 +0100 Subject: [PATCH 03/57] chore: migrate gci --- go.mod | 3 +- go.sum | 2 + pkg/golinters/gci/gci.go | 248 ++++--------------------- pkg/golinters/gci/internal/analyzer.go | 143 ++++++++++++++ pkg/golinters/gci/internal/errors.go | 11 ++ pkg/golinters/gci/testdata/gci.go | 5 +- 6 files changed, 194 insertions(+), 218 deletions(-) create mode 100644 pkg/golinters/gci/internal/analyzer.go create mode 100644 pkg/golinters/gci/internal/errors.go diff --git a/go.mod b/go.mod index f159592e262c..1dc12ad48d1d 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,6 @@ require ( github.com/gostaticanalysis/forcetypeassert v0.1.0 github.com/gostaticanalysis/nilerr v0.1.1 github.com/hashicorp/go-version v1.7.0 - github.com/hexops/gotextdiff v1.0.3 github.com/jgautheron/goconst v1.7.1 github.com/jingyugao/rowserrcheck v1.1.1 github.com/jjti/go-spancheck v0.6.4 @@ -157,12 +156,14 @@ require ( github.com/go-toolsmith/typep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/golangci/modinfo v0.3.3 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.6 // indirect diff --git a/go.sum b/go.sum index a195891fd2f8..f49e3d10f222 100644 --- a/go.sum +++ b/go.sum @@ -238,6 +238,8 @@ github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZD github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/modinfo v0.3.3 h1:YBQDZpDMJpe5mtd0klUFYL8tSVkmF3cmm0fZ48sc7+s= +github.com/golangci/modinfo v0.3.3/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= diff --git a/pkg/golinters/gci/gci.go b/pkg/golinters/gci/gci.go index 49b5ad61367d..dbbf4ca619ee 100644 --- a/pkg/golinters/gci/gci.go +++ b/pkg/golinters/gci/gci.go @@ -2,242 +2,60 @@ package gci import ( "fmt" - "sort" "strings" - "sync" - gcicfg "github.com/daixiang0/gci/pkg/config" - "github.com/daixiang0/gci/pkg/gci" - "github.com/daixiang0/gci/pkg/io" - "github.com/daixiang0/gci/pkg/log" - "github.com/daixiang0/gci/pkg/section" - "github.com/hexops/gotextdiff" - "github.com/hexops/gotextdiff/myers" - "github.com/hexops/gotextdiff/span" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/go/analysis/passes/inspect" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/golinters/gci/internal" ) const linterName = "gci" -func New(settings *config.GciSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue +const prefixSeparator = "ยค" - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, - Requires: []*analysis.Analyzer{ - inspect.Analyzer, - }, - } +func New(settings *config.GciSettings) *goanalysis.Linter { + a := internal.NewAnalyzer() - var cfg *gcicfg.Config + var cfg map[string]map[string]any if settings != nil { - rawCfg := gcicfg.YamlConfig{ - Cfg: gcicfg.BoolConfig{ - NoInlineComments: settings.NoInlineComments, - NoPrefixComments: settings.NoPrefixComments, - SkipGenerated: settings.SkipGenerated, - CustomOrder: settings.CustomOrder, - NoLexOrder: settings.NoLexOrder, - }, - SectionStrings: settings.Sections, - } - - if settings.LocalPrefixes != "" { - prefix := []string{"standard", "default", fmt.Sprintf("prefix(%s)", settings.LocalPrefixes)} - rawCfg.SectionStrings = prefix - } - - var err error - cfg, err = YamlConfig{origin: rawCfg}.Parse() - if err != nil { - internal.LinterLogger.Fatalf("gci: configuration parsing: %v", err) - } - } - - var lock sync.Mutex - - return goanalysis.NewLinter( - linterName, - "Gci controls Go package import order and makes it always deterministic.", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - analyzer.Run = func(pass *analysis.Pass) (any, error) { - cfg.Sections = hackSectionList(pass, cfg) - - issues, err := runGci(pass, lintCtx, cfg, &lock) - if err != nil { - return nil, err - } - - if len(issues) == 0 { - return nil, nil + var sections []string + for _, section := range settings.Sections { + if strings.HasPrefix(section, "prefix(") { + sections = append(sections, strings.ReplaceAll(section, ",", prefixSeparator)) + continue } - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runGci(pass *analysis.Pass, lintCtx *linter.Context, cfg *gcicfg.Config, lock *sync.Mutex) ([]goanalysis.Issue, error) { - fileNames := internal.GetFileNames(pass) - - var diffs []string - err := diffFormattedFilesToArray(fileNames, *cfg, &diffs, lock) - if err != nil { - return nil, err - } - - var issues []goanalysis.Issue - - for _, diff := range diffs { - if diff == "" { - continue - } - - is, err := internal.ExtractIssuesFromPatch(diff, lintCtx, linterName, getIssuedTextGci) - if err != nil { - return nil, fmt.Errorf("can't extract issues from gci diff output %s: %w", diff, err) + sections = append(sections, section) } - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) - } - } - - return issues, nil -} - -func getIssuedTextGci(settings *config.LintersSettings) string { - text := "File is not `gci`-ed" - - hasOptions := settings.Gci.SkipGenerated || len(settings.Gci.Sections) > 0 - if !hasOptions { - return text - } - - text += " with" - - if settings.Gci.SkipGenerated { - text += " --skip-generated" - } - - if len(settings.Gci.Sections) > 0 { - for _, sect := range settings.Gci.Sections { - text += " -s " + sect + cfg = map[string]map[string]any{ + a.Name: { + internal.NoInlineCommentsFlag: settings.NoInlineComments, + internal.NoPrefixCommentsFlag: settings.NoPrefixComments, + internal.SkipGeneratedFlag: settings.SkipGenerated, + internal.SectionsFlag: sections, // bug because prefix contains comas. + internal.CustomOrderFlag: settings.CustomOrder, + internal.NoLexOrderFlag: settings.NoLexOrder, + internal.PrefixDelimiterFlag: prefixSeparator, + }, } - } - - if settings.Gci.CustomOrder { - text += " --custom-order" - } - - return text -} -func hackSectionList(pass *analysis.Pass, cfg *gcicfg.Config) section.SectionList { - var sections section.SectionList - - for _, sect := range cfg.Sections { - // local module hack - if v, ok := sect.(*section.LocalModule); ok { - if pass.Module == nil { - continue + if settings.LocalPrefixes != "" { + prefix := []string{ + "standard", + "default", + fmt.Sprintf("prefix(%s)", strings.Join(strings.Split(settings.LocalPrefixes, ","), prefixSeparator)), } - - v.Path = pass.Module.Path + cfg[a.Name][internal.SectionsFlag] = prefix } - - sections = append(sections, sect) - } - - return sections -} - -// diffFormattedFilesToArray is a copy of gci.DiffFormattedFilesToArray without io.StdInGenerator. -// gci.DiffFormattedFilesToArray uses gci.processStdInAndGoFilesInPaths that uses io.StdInGenerator but stdin is not active on CI. -// https://github.com/daixiang0/gci/blob/6f5cb16718ba07f0342a58de9b830ec5a6d58790/pkg/gci/gci.go#L63-L75 -// https://github.com/daixiang0/gci/blob/6f5cb16718ba07f0342a58de9b830ec5a6d58790/pkg/gci/gci.go#L80 -func diffFormattedFilesToArray(paths []string, cfg gcicfg.Config, diffs *[]string, lock *sync.Mutex) error { - log.InitLogger() - defer func() { _ = log.L().Sync() }() - - return gci.ProcessFiles(io.GoFilesInPathsGenerator(paths, true), cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error { - fileURI := span.URIFromPath(filePath) - edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile)) - unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits) - lock.Lock() - *diffs = append(*diffs, fmt.Sprint(unifiedEdits)) - lock.Unlock() - return nil - }) -} - -// Code below this comment is borrowed and modified from gci. -// https://github.com/daixiang0/gci/blob/v0.13.5/pkg/config/config.go - -var defaultOrder = map[string]int{ - section.StandardType: 0, - section.DefaultType: 1, - section.CustomType: 2, - section.BlankType: 3, - section.DotType: 4, - section.AliasType: 5, - section.LocalModuleType: 6, -} - -type YamlConfig struct { - origin gcicfg.YamlConfig -} - -//nolint:gocritic // code borrowed from gci and modified to fix LocalModule section behavior. -func (g YamlConfig) Parse() (*gcicfg.Config, error) { - var err error - - sections, err := section.Parse(g.origin.SectionStrings) - if err != nil { - return nil, err } - if sections == nil { - sections = section.DefaultSections() - } - - // if default order sorted sections - if !g.origin.Cfg.CustomOrder { - sort.Slice(sections, func(i, j int) bool { - sectionI, sectionJ := sections[i].Type(), sections[j].Type() - - if g.origin.Cfg.NoLexOrder || strings.Compare(sectionI, sectionJ) != 0 { - return defaultOrder[sectionI] < defaultOrder[sectionJ] - } - - return strings.Compare(sections[i].String(), sections[j].String()) < 0 - }) - } - - sectionSeparators, err := section.Parse(g.origin.SectionSeparatorStrings) - if err != nil { - return nil, err - } - if sectionSeparators == nil { - sectionSeparators = section.DefaultSectionSeparators() - } - - return &gcicfg.Config{BoolConfig: g.origin.Cfg, Sections: sections, SectionSeparators: sectionSeparators}, nil + return goanalysis.NewLinter( + linterName, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeSyntax) } diff --git a/pkg/golinters/gci/internal/analyzer.go b/pkg/golinters/gci/internal/analyzer.go new file mode 100644 index 000000000000..3ace35d43195 --- /dev/null +++ b/pkg/golinters/gci/internal/analyzer.go @@ -0,0 +1,143 @@ +package internal + +import ( + "go/token" + "strings" + + "github.com/daixiang0/gci/pkg/analyzer" + "github.com/daixiang0/gci/pkg/log" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + + "github.com/daixiang0/gci/pkg/config" + "github.com/daixiang0/gci/pkg/gci" + "github.com/daixiang0/gci/pkg/io" +) + +const ( + NoInlineCommentsFlag = "noInlineComments" + NoPrefixCommentsFlag = "noPrefixComments" + SkipGeneratedFlag = "skipGenerated" + SectionsFlag = "Sections" + SectionSeparatorsFlag = "SectionSeparators" + NoLexOrderFlag = "NoLexOrder" + CustomOrderFlag = "CustomOrder" + PrefixDelimiterFlag = "PrefixDelimiter" +) + +const SectionDelimiter = "," + +var ( + noInlineComments bool + noPrefixComments bool + skipGenerated bool + sectionsStr string + sectionSeparatorsStr string + noLexOrder bool + customOrder bool + prefixDelimiter string +) + +func NewAnalyzer() *analysis.Analyzer { + log.InitLogger() + _ = log.L().Sync() + + a := &analysis.Analyzer{ + Name: "gci", + Doc: "A tool that control Go package import order and make it always deterministic.", + Run: runAnalysis, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } + + a.Flags.BoolVar(&noInlineComments, NoInlineCommentsFlag, false, + "If comments in the same line as the input should be present") + a.Flags.BoolVar(&noPrefixComments, NoPrefixCommentsFlag, false, + "If comments above an input should be present") + a.Flags.BoolVar(&skipGenerated, SkipGeneratedFlag, false, + "Skip generated files") + a.Flags.StringVar(§ionsStr, SectionsFlag, "", + "Specify the Sections format that should be used to check the file formatting") + a.Flags.StringVar(§ionSeparatorsStr, SectionSeparatorsFlag, "", + "Specify the Sections that are inserted as Separators between Sections") + a.Flags.BoolVar(&noLexOrder, NoLexOrderFlag, false, + "Drops lexical ordering for custom sections") + a.Flags.BoolVar(&customOrder, CustomOrderFlag, false, + "Enable custom order of sections") + + a.Flags.StringVar(&prefixDelimiter, PrefixDelimiterFlag, SectionDelimiter, "") + + return a +} + +func runAnalysis(pass *analysis.Pass) (any, error) { + var fileReferences []*token.File + // extract file references for all files in the analyzer pass + for _, pkgFile := range pass.Files { + fileForPos := pass.Fset.File(pkgFile.Package) + if fileForPos != nil { + fileReferences = append(fileReferences, fileForPos) + } + } + + expectedNumFiles := len(pass.Files) + foundNumFiles := len(fileReferences) + if expectedNumFiles != foundNumFiles { + return nil, InvalidNumberOfFilesInAnalysis{expectedNumFiles, foundNumFiles} + } + + gciCfg, err := generateGciConfiguration(pass.Module.Path).Parse() + if err != nil { + return nil, err + } + + for _, file := range fileReferences { + unmodifiedFile, formattedFile, err := gci.LoadFormatGoFile(io.File{FilePath: file.Name()}, *gciCfg) + if err != nil { + return nil, err + } + + fix, err := analyzer.GetSuggestedFix(file, unmodifiedFile, formattedFile) + if err != nil { + return nil, err + } + + if fix == nil { + // no difference + continue + } + + pass.Report(analysis.Diagnostic{ + Pos: fix.TextEdits[0].Pos, + Message: "Invalid import order", + SuggestedFixes: []analysis.SuggestedFix{*fix}, + }) + } + + return nil, nil +} + +func generateGciConfiguration(modPath string) *config.YamlConfig { + fmtCfg := config.BoolConfig{ + NoInlineComments: noInlineComments, + NoPrefixComments: noPrefixComments, + Debug: false, + SkipGenerated: skipGenerated, + CustomOrder: customOrder, + NoLexOrder: noLexOrder, + } + + var sectionStrings []string + if sectionsStr != "" { + s := strings.Split(sectionsStr, SectionDelimiter) + for _, a := range s { + sectionStrings = append(sectionStrings, strings.ReplaceAll(a, prefixDelimiter, SectionDelimiter)) + } + } + + var sectionSeparatorStrings []string + if sectionSeparatorsStr != "" { + sectionSeparatorStrings = strings.Split(sectionSeparatorsStr, SectionDelimiter) + } + + return &config.YamlConfig{Cfg: fmtCfg, SectionStrings: sectionStrings, SectionSeparatorStrings: sectionSeparatorStrings, ModPath: modPath} +} diff --git a/pkg/golinters/gci/internal/errors.go b/pkg/golinters/gci/internal/errors.go new file mode 100644 index 000000000000..c9c0630ed731 --- /dev/null +++ b/pkg/golinters/gci/internal/errors.go @@ -0,0 +1,11 @@ +package internal + +import "fmt" + +type InvalidNumberOfFilesInAnalysis struct { + expectedNumFiles, foundNumFiles int +} + +func (i InvalidNumberOfFilesInAnalysis) Error() string { + return fmt.Sprintf("Expected %d files in Analyzer input, Found %d", i.expectedNumFiles, i.foundNumFiles) +} diff --git a/pkg/golinters/gci/testdata/gci.go b/pkg/golinters/gci/testdata/gci.go index 4fa6c9e4b034..5b9a908e5d2d 100644 --- a/pkg/golinters/gci/testdata/gci.go +++ b/pkg/golinters/gci/testdata/gci.go @@ -2,12 +2,13 @@ //golangcitest:config_path testdata/gci.yml package testdata +// want +1 "Invalid import order" import ( - "golang.org/x/tools/go/analysis" // want "File is not \\`gci\\`-ed with --skip-generated -s standard -s prefix\\(github.com/golangci/golangci-lint,github.com/daixiang0/gci\\) -s default --custom-order" + "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "fmt" "errors" - gcicfg "github.com/daixiang0/gci/pkg/config" // want "File is not \\`gci\\`-ed with --skip-generated -s standard -s prefix\\(github.com/golangci/golangci-lint,github.com/daixiang0/gci\\) -s default --custom-order" + gcicfg "github.com/daixiang0/gci/pkg/config" ) func GoimportsLocalTest() { From d351aa8f59df790c95c5666a3d0abebcab60e16d Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:44:12 +0100 Subject: [PATCH 04/57] chore: migrate gocritic --- pkg/golinters/gocritic/gocritic.go | 74 +++++++++--------------------- 1 file changed, 21 insertions(+), 53 deletions(-) diff --git a/pkg/golinters/gocritic/gocritic.go b/pkg/golinters/gocritic/gocritic.go index 194ea3535575..087ddc1df067 100644 --- a/pkg/golinters/gocritic/gocritic.go +++ b/pkg/golinters/gocritic/gocritic.go @@ -5,7 +5,6 @@ import ( "fmt" "go/ast" "go/types" - "path/filepath" "reflect" "runtime" "slices" @@ -23,7 +22,6 @@ import ( "github.com/golangci/golangci-lint/pkg/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "gocritic" @@ -34,9 +32,6 @@ var ( ) func New(settings *config.GoCriticSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - wrapper := &goCriticWrapper{ sizes: types.SizesFor("gc", runtime.GOARCH), } @@ -45,19 +40,11 @@ func New(settings *config.GoCriticSettings) *goanalysis.Linter { Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := wrapper.run(pass) + err := wrapper.run(pass) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil }, } @@ -75,9 +62,6 @@ Dynamic rules are written declaratively with AST patterns, filters, report messa wrapper.init(context.Log, settings) }). - WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }). WithLoadMode(goanalysis.LoadModeTypesInfo) } @@ -111,9 +95,9 @@ func (w *goCriticWrapper) init(logger logutils.Log, settings *config.GoCriticSet w.settingsWrapper = settingsWrapper } -func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { +func (w *goCriticWrapper) run(pass *analysis.Pass) error { if w.settingsWrapper == nil { - return nil, errors.New("the settings wrapper is nil") + return errors.New("the settings wrapper is nil") } linterCtx := gocriticlinter.NewContext(pass.Fset, w.sizes) @@ -122,19 +106,14 @@ func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) { enabledCheckers, err := w.buildEnabledCheckers(linterCtx) if err != nil { - return nil, err + return err } linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg) - pkgIssues := runOnPackage(linterCtx, enabledCheckers, pass.Files) + runOnPackage(pass, enabledCheckers, pass.Files) - issues := make([]goanalysis.Issue, 0, len(pkgIssues)) - for i := range pkgIssues { - issues = append(issues, goanalysis.NewIssue(&pkgIssues[i], pass)) - } - - return issues, nil + return nil } func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) { @@ -214,47 +193,36 @@ func (w *goCriticWrapper) normalizeCheckerParamsValue(p any) any { } } -func runOnPackage(linterCtx *gocriticlinter.Context, checks []*gocriticlinter.Checker, files []*ast.File) []result.Issue { - var res []result.Issue +func runOnPackage(pass *analysis.Pass, checks []*gocriticlinter.Checker, files []*ast.File) { for _, f := range files { - filename := filepath.Base(linterCtx.FileSet.Position(f.Pos()).Filename) - linterCtx.SetFileInfo(filename, f) - - issues := runOnFile(linterCtx, f, checks) - res = append(res, issues...) + runOnFile(pass, f, checks) } - return res } -func runOnFile(linterCtx *gocriticlinter.Context, f *ast.File, checks []*gocriticlinter.Checker) []result.Issue { - var res []result.Issue - +func runOnFile(pass *analysis.Pass, f *ast.File, checks []*gocriticlinter.Checker) { for _, c := range checks { // All checkers are expected to use *lint.Context // as read-only structure, so no copying is required. for _, warn := range c.Check(f) { - pos := linterCtx.FileSet.Position(warn.Pos) - issue := result.Issue{ - Pos: pos, - Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), - FromLinter: linterName, + diag := analysis.Diagnostic{ + Pos: warn.Pos, + Category: c.Info.Name, + Message: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), } if warn.HasQuickFix() { - issue.Replacement = &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: pos.Column - 1, - Length: int(warn.Suggestion.To - warn.Suggestion.From), - NewString: string(warn.Suggestion.Replacement), - }, - } + diag.SuggestedFixes = []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: warn.Suggestion.From, + End: warn.Suggestion.To, + NewText: warn.Suggestion.Replacement, + }}, + }} } - res = append(res, issue) + pass.Report(diag) } } - - return res } type goCriticChecks[T any] map[string]T From 36b254978f555c563863a572d7aba0c048c7ed8f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:46:03 +0100 Subject: [PATCH 05/57] chore: migrate godot --- pkg/golinters/godot/godot.go | 68 ++++++++++++--------------- pkg/golinters/godot/testdata/godot.go | 4 +- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/pkg/golinters/godot/godot.go b/pkg/golinters/godot/godot.go index 6b6adef53b28..2d8135393de7 100644 --- a/pkg/golinters/godot/godot.go +++ b/pkg/golinters/godot/godot.go @@ -2,23 +2,18 @@ package godot import ( "cmp" - "sync" + "go/token" "github.com/tetafro/godot" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "godot" func New(settings *config.GodotSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - var dotSettings godot.Settings if settings != nil { @@ -41,19 +36,11 @@ func New(settings *config.GodotSettings) *goanalysis.Linter { Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runGodot(pass, dotSettings) + err := runGodot(pass, dotSettings) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil }, } @@ -63,38 +50,43 @@ func New(settings *config.GodotSettings) *goanalysis.Linter { "Check if comments end in a period", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runGodot(pass *analysis.Pass, settings godot.Settings) ([]goanalysis.Issue, error) { - var lintIssues []godot.Issue +func runGodot(pass *analysis.Pass, settings godot.Settings) error { for _, file := range pass.Files { iss, err := godot.Run(file, pass.Fset, settings) if err != nil { - return nil, err + return err } - lintIssues = append(lintIssues, iss...) - } - if len(lintIssues) == 0 { - return nil, nil - } + for _, i := range iss { + f := pass.Fset.File(file.Pos()) - issues := make([]goanalysis.Issue, len(lintIssues)) - for k, i := range lintIssues { - issue := result.Issue{ - Pos: i.Pos, - Text: i.Message, - FromLinter: linterName, - Replacement: &result.Replacement{ - NewLines: []string{i.Replacement}, - }, - } + pos := f.Pos(i.Pos.Offset) - issues[k] = goanalysis.NewIssue(&issue, pass) + var end token.Pos + if i.Pos.Line == f.LineCount() { + // missing newline at the end of the file + end = f.Pos(f.Size()) + } else { + end = f.LineStart(i.Pos.Line+1) - token.Pos(1) + } + + pass.Report(analysis.Diagnostic{ + Pos: pos, + End: end, + Message: i.Message, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: pos, + End: end, + NewText: []byte(i.Replacement), + }}, + }}, + }) + } } - return issues, nil + return nil } diff --git a/pkg/golinters/godot/testdata/godot.go b/pkg/golinters/godot/testdata/godot.go index 2cfc41b5abb7..93a904894691 100644 --- a/pkg/golinters/godot/testdata/godot.go +++ b/pkg/golinters/godot/testdata/godot.go @@ -1,7 +1,9 @@ //golangcitest:args -Egodot package testdata -// Godot checks top-level comments // want "Comment should end in a period" +// want +2 "Comment should end in a period" + +// Godot checks top-level comments func Godot() { // nothing to do here } From 12d2c139d06baad453bc24ed377e64700d16ad49 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:46:33 +0100 Subject: [PATCH 06/57] chore: migrate goheader --- pkg/golinters/goheader/goheader.go | 76 ++++++++----------- .../goheader/testdata/fix/out/goheader_1.go | 2 +- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/pkg/golinters/goheader/goheader.go b/pkg/golinters/goheader/goheader.go index c6b1aae6be4d..49280365142a 100644 --- a/pkg/golinters/goheader/goheader.go +++ b/pkg/golinters/goheader/goheader.go @@ -2,23 +2,18 @@ package goheader import ( "go/token" - "sync" + "strings" goheader "github.com/denis-tingaikin/go-header" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "goheader" func New(settings *config.GoHeaderSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - conf := &goheader.Configuration{} if settings != nil { conf = &goheader.Configuration{ @@ -32,19 +27,11 @@ func New(settings *config.GoHeaderSettings) *goanalysis.Linter { Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runGoHeader(pass, conf) + err := runGoHeader(pass, conf) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil }, } @@ -54,62 +41,61 @@ func New(settings *config.GoHeaderSettings) *goanalysis.Linter { "Checks if file header matches to pattern", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runGoHeader(pass *analysis.Pass, conf *goheader.Configuration) ([]goanalysis.Issue, error) { +func runGoHeader(pass *analysis.Pass, conf *goheader.Configuration) error { if conf.TemplatePath == "" && conf.Template == "" { // User did not pass template, so then do not run go-header linter - return nil, nil + return nil } template, err := conf.GetTemplate() if err != nil { - return nil, err + return err } values, err := conf.GetValues() if err != nil { - return nil, err + return err } a := goheader.New(goheader.WithTemplate(template), goheader.WithValues(values)) - var issues []goanalysis.Issue for _, file := range pass.Files { - path := pass.Fset.Position(file.Pos()).Filename - - i := a.Analyze(&goheader.Target{File: file, Path: path}) + position := goanalysis.GetFilePosition(pass, file) - if i == nil { + issue := a.Analyze(&goheader.Target{File: file, Path: position.Filename}) + if issue == nil { continue } - issue := result.Issue{ - Pos: token.Position{ - Line: i.Location().Line + 1, - Column: i.Location().Position, - Filename: path, - }, - Text: i.Message(), - FromLinter: linterName, + f := pass.Fset.File(file.Pos()) + + start := f.LineStart(issue.Location().Line + 1) + + diag := analysis.Diagnostic{ + Pos: start, + Message: issue.Message(), } - if fix := i.Fix(); fix != nil { - issue.LineRange = &result.Range{ - From: issue.Line(), - To: issue.Line() + len(fix.Actual) - 1, - } - issue.Replacement = &result.Replacement{ - NeedOnlyDelete: len(fix.Expected) == 0, - NewLines: fix.Expected, + if fix := issue.Fix(); fix != nil { + end := len(fix.Actual) + for _, s := range fix.Actual { + end += len(s) } + + diag.SuggestedFixes = []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: start, + End: start + token.Pos(end), + NewText: []byte(strings.Join(fix.Expected, "\n") + "\n"), + }}, + }} } - issues = append(issues, goanalysis.NewIssue(&issue, pass)) + pass.Report(diag) } - return issues, nil + return nil } diff --git a/pkg/golinters/goheader/testdata/fix/out/goheader_1.go b/pkg/golinters/goheader/testdata/fix/out/goheader_1.go index fbc9a7f3b1a6..d17ec18c0480 100644 --- a/pkg/golinters/goheader/testdata/fix/out/goheader_1.go +++ b/pkg/golinters/goheader/testdata/fix/out/goheader_1.go @@ -1,5 +1,5 @@ // Copyright 2024 The Awesome Project Authors -// +// // Use of this source code is governed by LICENSE //golangcitest:args -Egoheader From 30451edc2dd0c50b7cff62818f357dafe4045125 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:47:17 +0100 Subject: [PATCH 07/57] chore: migrate mirror --- pkg/golinters/mirror/mirror.go | 64 +++++++--------------------------- 1 file changed, 12 insertions(+), 52 deletions(-) diff --git a/pkg/golinters/mirror/mirror.go b/pkg/golinters/mirror/mirror.go index 34b880b5295f..e15dfa3a5a58 100644 --- a/pkg/golinters/mirror/mirror.go +++ b/pkg/golinters/mirror/mirror.go @@ -1,70 +1,30 @@ package mirror import ( - "sync" - "github.com/butuzov/mirror" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) func New() *goanalysis.Linter { - var ( - mu sync.Mutex - issues []goanalysis.Issue - ) - a := mirror.NewAnalyzer() - a.Run = func(pass *analysis.Pass) (any, error) { - // mirror only lints test files if the `--with-tests` flag is passed, - // so we pass the `with-tests` flag as true to the analyzer before running it. - // This can be turned off by using the regular golangci-lint flags such as `--tests` or `--skip-files` - // or can be disabled per linter via exclude rules. - // (see https://github.com/golangci/golangci-lint/issues/2527#issuecomment-1023707262) - violations := mirror.Run(pass, true) - - if len(violations) == 0 { - return nil, nil - } - - for index := range violations { - i := violations[index].Issue(pass.Fset) - issue := result.Issue{ - FromLinter: a.Name, - Text: i.Message, - Pos: i.Start, - } - - if i.InlineFix != "" { - issue.Replacement = &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: i.Start.Column - 1, - Length: len(i.Original), - NewString: i.InlineFix, - }, - } - } - - mu.Lock() - issues = append(issues, goanalysis.NewIssue(&issue, pass)) - mu.Unlock() - } - - return nil, nil + // mirror only lints test files if the `--with-tests` flag is passed, + // so we pass the `with-tests` flag as true to the analyzer before running it. + // This can be turned off by using the regular golangci-lint flags such as `--tests` or `--skip-files` + // or can be disabled per linter via exclude rules. + // (see https://github.com/golangci/golangci-lint/issues/2527#issuecomment-1023707262) + linterCfg := map[string]map[string]any{ + a.Name: { + "with-tests": true, + }, } - analyzer := goanalysis.NewLinter( + return goanalysis.NewLinter( a.Name, a.Doc, []*analysis.Analyzer{a}, - nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return issues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) - - return analyzer + linterCfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } From 6a400690ee99863a8c588afc5ac2d547ff54dd4f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:47:45 +0100 Subject: [PATCH 08/57] chore: migrate protogetter --- pkg/golinters/protogetter/protogetter.go | 48 ++---------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/pkg/golinters/protogetter/protogetter.go b/pkg/golinters/protogetter/protogetter.go index 302ce67b88f4..6c65f86bcfff 100644 --- a/pkg/golinters/protogetter/protogetter.go +++ b/pkg/golinters/protogetter/protogetter.go @@ -1,21 +1,14 @@ package protogetter import ( - "sync" - "github.com/ghostiam/protogetter" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) func New(settings *config.ProtoGetterSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - var cfg protogetter.Config if settings != nil { cfg = protogetter.Config{ @@ -25,50 +18,15 @@ func New(settings *config.ProtoGetterSettings) *goanalysis.Linter { ReplaceFirstArgInAppend: settings.ReplaceFirstArgInAppend, } } - cfg.Mode = protogetter.GolangciLintMode - - a := protogetter.NewAnalyzer(&cfg) - a.Run = func(pass *analysis.Pass) (any, error) { - pgIssues, err := protogetter.Run(pass, &cfg) - if err != nil { - return nil, err - } - - issues := make([]goanalysis.Issue, len(pgIssues)) - for i, issue := range pgIssues { - report := &result.Issue{ - FromLinter: a.Name, - Pos: issue.Pos, - Text: issue.Message, - Replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: issue.InlineFix.StartCol, - Length: issue.InlineFix.Length, - NewString: issue.InlineFix.NewString, - }, - }, - } - issues[i] = goanalysis.NewIssue(report, pass) - } - - if len(issues) == 0 { - return nil, nil - } + cfg.Mode = protogetter.StandaloneMode - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - } + a := protogetter.NewAnalyzer(&cfg) return goanalysis.NewLinter( a.Name, a.Doc, []*analysis.Analyzer{a}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } From ebcff940b1eda805f3f9443a3d616bde7e1af8ac Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:48:14 +0100 Subject: [PATCH 09/57] chore: migrate tagalign --- pkg/golinters/tagalign/tagalign.go | 44 ++---------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/pkg/golinters/tagalign/tagalign.go b/pkg/golinters/tagalign/tagalign.go index f438c51b5c7e..9c4b39ffe80b 100644 --- a/pkg/golinters/tagalign/tagalign.go +++ b/pkg/golinters/tagalign/tagalign.go @@ -1,22 +1,15 @@ package tagalign import ( - "sync" - "github.com/4meepo/tagalign" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) func New(settings *config.TagAlignSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - - options := []tagalign.Option{tagalign.WithMode(tagalign.GolangciLintMode)} + options := []tagalign.Option{tagalign.WithMode(tagalign.StandaloneMode)} if settings != nil { options = append(options, tagalign.WithAlign(settings.Align)) @@ -32,44 +25,11 @@ func New(settings *config.TagAlignSettings) *goanalysis.Linter { } analyzer := tagalign.NewAnalyzer(options...) - analyzer.Run = func(pass *analysis.Pass) (any, error) { - taIssues := tagalign.Run(pass, options...) - - issues := make([]goanalysis.Issue, len(taIssues)) - for i, issue := range taIssues { - report := &result.Issue{ - FromLinter: analyzer.Name, - Pos: issue.Pos, - Text: issue.Message, - Replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: issue.InlineFix.StartCol, - Length: issue.InlineFix.Length, - NewString: issue.InlineFix.NewString, - }, - }, - } - - issues[i] = goanalysis.NewIssue(report, pass) - } - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - } return goanalysis.NewLinter( analyzer.Name, analyzer.Doc, []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } From 89dc119f5a8b8f61617c44580d8a2c8db84cefc8 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:48:30 +0100 Subject: [PATCH 10/57] chore: migrate whitespace --- pkg/golinters/whitespace/whitespace.go | 77 +------------------------- 1 file changed, 2 insertions(+), 75 deletions(-) diff --git a/pkg/golinters/whitespace/whitespace.go b/pkg/golinters/whitespace/whitespace.go index 721bfada1c6f..89599b39552d 100644 --- a/pkg/golinters/whitespace/whitespace.go +++ b/pkg/golinters/whitespace/whitespace.go @@ -1,28 +1,18 @@ package whitespace import ( - "fmt" - "sync" - "github.com/ultraware/whitespace" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) -const linterName = "whitespace" - func New(settings *config.WhitespaceSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - var wsSettings whitespace.Settings if settings != nil { wsSettings = whitespace.Settings{ - Mode: whitespace.RunningModeGolangCI, + Mode: whitespace.RunningModeNative, MultiIf: settings.MultiIf, MultiFunc: settings.MultiFunc, } @@ -35,68 +25,5 @@ func New(settings *config.WhitespaceSettings) *goanalysis.Linter { a.Doc, []*analysis.Analyzer{a}, nil, - ).WithContextSetter(func(_ *linter.Context) { - a.Run = func(pass *analysis.Pass) (any, error) { - issues, err := runWhitespace(pass, wsSettings) - if err != nil { - return nil, err - } - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - - return nil, nil - } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runWhitespace(pass *analysis.Pass, wsSettings whitespace.Settings) ([]goanalysis.Issue, error) { - lintIssues := whitespace.Run(pass, &wsSettings) - - issues := make([]goanalysis.Issue, len(lintIssues)) - for i, issue := range lintIssues { - report := &result.Issue{ - FromLinter: linterName, - Pos: pass.Fset.PositionFor(issue.Diagnostic, false), - Text: issue.Message, - } - - switch issue.MessageType { - case whitespace.MessageTypeRemove: - if len(issue.LineNumbers) == 0 { - continue - } - - report.LineRange = &result.Range{ - From: issue.LineNumbers[0], - To: issue.LineNumbers[len(issue.LineNumbers)-1], - } - - report.Replacement = &result.Replacement{NeedOnlyDelete: true} - - case whitespace.MessageTypeAdd: - report.Pos = pass.Fset.PositionFor(issue.FixStart, false) - report.Replacement = &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 0, - Length: 1, - NewString: "\n\t", - }, - } - - default: - return nil, fmt.Errorf("unknown message type: %v", issue.MessageType) - } - - issues[i] = goanalysis.NewIssue(report, pass) - } - - return issues, nil + ).WithLoadMode(goanalysis.LoadModeSyntax) } From 0acc99818a3ad53b5f83e713ea89526a096616ff Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:54:57 +0100 Subject: [PATCH 11/57] feat: add GetFilePositionFor --- pkg/goanalysis/position.go | 44 ++++++++++++++++++++++++++++++++++ pkg/golinters/internal/util.go | 14 ++++------- 2 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 pkg/goanalysis/position.go diff --git a/pkg/goanalysis/position.go b/pkg/goanalysis/position.go new file mode 100644 index 000000000000..f90a70983573 --- /dev/null +++ b/pkg/goanalysis/position.go @@ -0,0 +1,44 @@ +package goanalysis + +import ( + "go/ast" + "go/token" + "path/filepath" + + "golang.org/x/tools/go/analysis" +) + +func GetFilePosition(pass *analysis.Pass, f *ast.File) token.Position { + return GetFilePositionFor(pass.Fset, f.Pos()) +} + +func GetFilePositionFor(fset *token.FileSet, p token.Pos) token.Position { + pos := fset.PositionFor(p, true) + + ext := filepath.Ext(pos.Filename) + if ext != ".go" { + // position has been adjusted to a non-go file, revert to original file + return fset.PositionFor(p, false) + } + + return pos +} + +func EndOfLinePos(f *token.File, line int) token.Pos { + var end token.Pos + + if line == f.LineCount() { + // missing newline at the end of the file + end = f.Pos(f.Size()) + } else { + end = f.LineStart(line+1) - token.Pos(1) + } + + return end +} + +// AdjustPos is a hack to get the right line to display. +// It should not be used outside some specific cases. +func AdjustPos(line, nonAdjLine, adjLine int) int { + return line + nonAdjLine - adjLine +} diff --git a/pkg/golinters/internal/util.go b/pkg/golinters/internal/util.go index 80b194dd26db..ded7f9ac9510 100644 --- a/pkg/golinters/internal/util.go +++ b/pkg/golinters/internal/util.go @@ -2,12 +2,12 @@ package internal import ( "fmt" - "path/filepath" "strings" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goanalysis" ) func FormatCode(code string, _ *config.Config) string { @@ -19,15 +19,9 @@ func FormatCode(code string, _ *config.Config) string { } func GetFileNames(pass *analysis.Pass) []string { - var fileNames []string + var filenames []string for _, f := range pass.Files { - fileName := pass.Fset.PositionFor(f.Pos(), true).Filename - ext := filepath.Ext(fileName) - if ext != "" && ext != ".go" { - // position has been adjusted to a non-go file, revert to original file - fileName = pass.Fset.PositionFor(f.Pos(), false).Filename - } - fileNames = append(fileNames, fileName) + filenames = append(filenames, goanalysis.GetFilePosition(pass, f).Filename) } - return fileNames + return filenames } From 19a66e65c644ef7be110e1a57966ca3b8d8225d4 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 13 Dec 2024 16:50:16 +0100 Subject: [PATCH 12/57] chore: rewrite diff hunkChangesParser --- pkg/golinters/internal/diff.go | 249 +++++----- pkg/golinters/internal/diff_test.go | 449 ++++++------------ pkg/golinters/internal/testdata/add_only.diff | 11 + .../testdata/add_only_different_lines.diff | 17 + .../testdata/add_only_in_all_diff.diff | 9 + .../testdata/add_only_multiple_lines.diff | 14 + .../testdata/add_only_on_first_line.diff | 9 + ..._first_line_with_shared_original_line.diff | 11 + .../testdata/delete_only_first_lines.diff | 10 + .../internal/testdata/gofmt_diff.diff | 18 + .../internal/testdata/replace_line.diff | 9 + .../replace_line_after_first_line_adding.diff | 13 + 12 files changed, 388 insertions(+), 431 deletions(-) create mode 100644 pkg/golinters/internal/testdata/add_only.diff create mode 100644 pkg/golinters/internal/testdata/add_only_different_lines.diff create mode 100644 pkg/golinters/internal/testdata/add_only_in_all_diff.diff create mode 100644 pkg/golinters/internal/testdata/add_only_multiple_lines.diff create mode 100644 pkg/golinters/internal/testdata/add_only_on_first_line.diff create mode 100644 pkg/golinters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff create mode 100644 pkg/golinters/internal/testdata/delete_only_first_lines.diff create mode 100644 pkg/golinters/internal/testdata/gofmt_diff.diff create mode 100644 pkg/golinters/internal/testdata/replace_line.diff create mode 100644 pkg/golinters/internal/testdata/replace_line_after_first_line_adding.diff diff --git a/pkg/golinters/internal/diff.go b/pkg/golinters/internal/diff.go index f919c5b2a8f2..eafb88789295 100644 --- a/pkg/golinters/internal/diff.go +++ b/pkg/golinters/internal/diff.go @@ -3,20 +3,23 @@ package internal import ( "bytes" "fmt" + "go/ast" "go/token" + "slices" "strings" diffpkg "github.com/sourcegraph/go-diff/diff" + "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/result" ) type Change struct { - LineRange result.Range - Replacement result.Replacement + From, To int + NewLines []string } type diffLineType string @@ -44,58 +47,48 @@ type hunkChangesParser struct { log logutils.Log - lines []diffLine - - ret []Change + changes []Change } -func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) { - lines := bytes.Split(h.Body, []byte{'\n'}) - currentOriginalLineNumber := int(h.OrigStartLine) - var ret []diffLine +func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change { + lines := parseDiffLines(h) - for i, line := range lines { - dl := diffLine{ - originalNumber: currentOriginalLineNumber, + for i := 0; i < len(lines); { + line := lines[i] + + if line.typ == diffLineOriginal { + p.handleOriginalLine(lines, line, &i) + continue } - lineStr := string(line) + var deletedLines []diffLine + for ; i < len(lines) && lines[i].typ == diffLineDeleted; i++ { + deletedLines = append(deletedLines, lines[i]) + } - if strings.HasPrefix(lineStr, "-") { - dl.typ = diffLineDeleted - dl.data = strings.TrimPrefix(lineStr, "-") - currentOriginalLineNumber++ - } else if strings.HasPrefix(lineStr, "+") { - dl.typ = diffLineAdded - dl.data = strings.TrimPrefix(lineStr, "+") - } else { - if i == len(lines)-1 && lineStr == "" { - // handle last \n: don't add an empty original line - break - } + var addedLines []string + for ; i < len(lines) && lines[i].typ == diffLineAdded; i++ { + addedLines = append(addedLines, lines[i].data) + } - dl.typ = diffLineOriginal - dl.data = strings.TrimPrefix(lineStr, " ") - currentOriginalLineNumber++ + if len(deletedLines) != 0 { + p.handleDeletedLines(deletedLines, addedLines) + continue } - ret = append(ret, dl) + // no deletions, only additions + p.handleAddedOnlyLines(addedLines) } - // if > 0, then the original file had a 'No newline at end of file' mark - if h.OrigNoNewlineAt > 0 { - dl := diffLine{ - originalNumber: currentOriginalLineNumber + 1, - typ: diffLineAdded, - data: "", - } - ret = append(ret, dl) + if len(p.replacementLinesToPrepend) != 0 { + p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", lines) + return nil } - p.lines = ret + return p.changes } -func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) { +func (p *hunkChangesParser) handleOriginalLine(lines []diffLine, line diffLine, i *int) { if len(p.replacementLinesToPrepend) == 0 { p.lastOriginalLine = &line *i++ @@ -109,51 +102,39 @@ func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) { *i++ var followingAddedLines []string - for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ { - followingAddedLines = append(followingAddedLines, p.lines[*i].data) + for ; *i < len(lines) && lines[*i].typ == diffLineAdded; *i++ { + followingAddedLines = append(followingAddedLines, lines[*i].data) + } + + change := Change{ + From: line.originalNumber, + To: line.originalNumber, + NewLines: slices.Concat(p.replacementLinesToPrepend, []string{line.data}, followingAddedLines), } + p.changes = append(p.changes, change) - p.ret = append(p.ret, Change{ - LineRange: result.Range{ - From: line.originalNumber, - To: line.originalNumber, - }, - Replacement: result.Replacement{ - NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...), - }, - }) p.replacementLinesToPrepend = nil p.lastOriginalLine = &line } func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) { change := Change{ - LineRange: result.Range{ - From: deletedLines[0].originalNumber, - To: deletedLines[len(deletedLines)-1].originalNumber, - }, + From: deletedLines[0].originalNumber, + To: deletedLines[len(deletedLines)-1].originalNumber, } - if len(addedLines) != 0 { - change.Replacement.NewLines = append([]string{}, p.replacementLinesToPrepend...) - change.Replacement.NewLines = append(change.Replacement.NewLines, addedLines...) - if len(p.replacementLinesToPrepend) != 0 { - p.replacementLinesToPrepend = nil - } - - p.ret = append(p.ret, change) - return - } + switch { + case len(addedLines) != 0: + change.NewLines = slices.Concat(p.replacementLinesToPrepend, addedLines) + p.replacementLinesToPrepend = nil - // delete-only change with possible prepending - if len(p.replacementLinesToPrepend) != 0 { - change.Replacement.NewLines = p.replacementLinesToPrepend + case len(p.replacementLinesToPrepend) != 0: + // delete-only change with possible prepending + change.NewLines = slices.Clone(p.replacementLinesToPrepend) p.replacementLinesToPrepend = nil - } else { - change.Replacement.NeedOnlyDelete = true } - p.ret = append(p.ret, change) + p.changes = append(p.changes, change) } func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) { @@ -166,70 +147,88 @@ func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) { // 2. ... p.replacementLinesToPrepend = addedLines + return } // add-only change merged into the last original line with possible prepending - p.ret = append(p.ret, Change{ - LineRange: result.Range{ - From: p.lastOriginalLine.originalNumber, - To: p.lastOriginalLine.originalNumber, - }, - Replacement: result.Replacement{ - NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...), - }, - }) + change := Change{ + From: p.lastOriginalLine.originalNumber, + To: p.lastOriginalLine.originalNumber, + NewLines: slices.Concat(p.replacementLinesToPrepend, []string{p.lastOriginalLine.data}, addedLines), + } + + p.changes = append(p.changes, change) + p.replacementLinesToPrepend = nil } -func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change { - p.parseDiffLines(h) +func parseDiffLines(h *diffpkg.Hunk) []diffLine { + lines := bytes.Split(h.Body, []byte{'\n'}) - for i := 0; i < len(p.lines); { - line := p.lines[i] - if line.typ == diffLineOriginal { - p.handleOriginalLine(line, &i) - continue - } + currentOriginalLineNumber := int(h.OrigStartLine) - var deletedLines []diffLine - for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ { - deletedLines = append(deletedLines, p.lines[i]) + var diffLines []diffLine + + for i, line := range lines { + dl := diffLine{ + originalNumber: currentOriginalLineNumber, } - var addedLines []string - for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ { - addedLines = append(addedLines, p.lines[i].data) + if i == len(lines)-1 && len(line) == 0 { + // handle last \n: don't add an empty original line + break } - if len(deletedLines) != 0 { - p.handleDeletedLines(deletedLines, addedLines) - continue + lineStr := string(line) + + switch { + case strings.HasPrefix(lineStr, "-"): + dl.typ = diffLineDeleted + dl.data = strings.TrimPrefix(lineStr, "-") + currentOriginalLineNumber++ + + case strings.HasPrefix(lineStr, "+"): + dl.typ = diffLineAdded + dl.data = strings.TrimPrefix(lineStr, "+") + + default: + dl.typ = diffLineOriginal + dl.data = strings.TrimPrefix(lineStr, " ") + currentOriginalLineNumber++ } - // no deletions, only additions - p.handleAddedOnlyLines(addedLines) + diffLines = append(diffLines, dl) } - if len(p.replacementLinesToPrepend) != 0 { - p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines) - return nil + // if > 0, then the original file had a 'No newline at end of file' mark + if h.OrigNoNewlineAt > 0 { + dl := diffLine{ + originalNumber: currentOriginalLineNumber + 1, + typ: diffLineAdded, + data: "", + } + diffLines = append(diffLines, dl) } - return p.ret + return diffLines } -func ExtractIssuesFromPatch(patch string, lintCtx *linter.Context, linterName string, formatter fmtTextFormatter) ([]result.Issue, error) { +func ExtractDiagnosticFromPatch(pass *analysis.Pass, file *ast.File, patch string, + lintCtx *linter.Context, formatter fmtTextFormatter) error { diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch)) if err != nil { - return nil, fmt.Errorf("can't parse patch: %w", err) + return fmt.Errorf("can't parse patch: %w", err) } if len(diffs) == 0 { - return nil, fmt.Errorf("got no diffs from patch parser: %v", patch) + return fmt.Errorf("got no diffs from patch parser: %v", patch) } - var issues []result.Issue + ft := pass.Fset.File(file.Pos()) + + adjLine := pass.Fset.PositionFor(file.Pos(), false).Line - pass.Fset.PositionFor(file.Pos(), true).Line + for _, d := range diffs { if len(d.Hunks) == 0 { lintCtx.Log.Warnf("Got no hunks in diff %+v", d) @@ -242,23 +241,29 @@ func ExtractIssuesFromPatch(patch string, lintCtx *linter.Context, linterName st changes := p.parse(hunk) for _, change := range changes { - i := result.Issue{ - FromLinter: linterName, - Pos: token.Position{ - Filename: d.NewName, - Line: change.LineRange.From, - }, - Text: formatter(lintCtx.Settings()), - Replacement: &change.Replacement, - } - if change.LineRange.From != change.LineRange.To { - i.LineRange = &change.LineRange - } - - issues = append(issues, i) + pass.Report(toDiagnostic(ft, change, formatter(lintCtx.Settings()), adjLine)) } } } - return issues, nil + return nil +} + +func toDiagnostic(ft *token.File, change Change, message string, adjLine int) analysis.Diagnostic { + start := ft.LineStart(change.From + adjLine) + + end := goanalysis.EndOfLinePos(ft, change.To+adjLine) + + return analysis.Diagnostic{ + Pos: start, + End: end, + Message: message, // TODO(ldez) change message formatter to have a better message. + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: start, + End: end, + NewText: []byte(strings.Join(change.NewLines, "\n")), + }}, + }}, + } } diff --git a/pkg/golinters/internal/diff_test.go b/pkg/golinters/internal/diff_test.go index 7aa55578258c..915832b033cf 100644 --- a/pkg/golinters/internal/diff_test.go +++ b/pkg/golinters/internal/diff_test.go @@ -1,6 +1,8 @@ package internal import ( + "os" + "path/filepath" "testing" diffpkg "github.com/sourcegraph/go-diff/diff" @@ -9,343 +11,172 @@ import ( "github.com/stretchr/testify/require" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/result" ) -func testDiffProducesChanges(t *testing.T, log logutils.Log, diff string, expectedChanges ...Change) { - diffs, err := diffpkg.ParseMultiFileDiff([]byte(diff)) - if err != nil { - require.NoError(t, err) - } - - require.Len(t, diffs, 1) - hunks := diffs[0].Hunks - assert.NotEmpty(t, hunks) - - var changes []Change - for _, hunk := range hunks { - p := hunkChangesParser{ - log: log, - } - changes = append(changes, p.parse(hunk)...) - } - - assert.Equal(t, expectedChanges, changes) -} - -func TestExtractChangesFromHunkAddOnly(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..43d04bf 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -1,5 +1,6 @@ - package logutil - -+// added line - type Func func(format string, args ...interface{}) - - type Log interface { -` - - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 2, - To: 2, - }, - Replacement: result.Replacement{ - NewLines: []string{ - "", - "// added line", - }, - }, - }) -} - -func TestExtractChangesFromHunkAddOnlyOnFirstLine(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..97e6660 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -1,3 +1,4 @@ -+// added line - package logutil - - type Func func(format string, args ...interface{}) -` - - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 1, - To: 1, - }, - Replacement: result.Replacement{ - NewLines: []string{ - "// added line", - "package logutil", - }, +func Test_parse(t *testing.T) { + testCases := []struct { + diff string + log logutils.Log + expected []Change + }{ + { + diff: "delete_only_first_lines.diff", + expected: []Change{{ + From: 1, + To: 2, + }}, }, - }) -} - -func TestExtractChangesFromHunkAddOnlyOnFirstLineWithSharedOriginalLine(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..7ff80c9 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -1,4 +1,7 @@ -+// added line 1 - package logutil -+// added line 2 -+// added line 3 - - type Func func(format string, args ...interface{}) -` - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 1, - To: 1, + { + diff: "add_only.diff", + expected: []Change{{ + From: 2, + To: 2, + NewLines: []string{ + "", + "// added line", + }, + }}, }, - Replacement: result.Replacement{ - NewLines: []string{ - "// added line 1", - "package logutil", - "// added line 2", - "// added line 3", + { + diff: "add_only_different_lines.diff", + expected: []Change{ + { + From: 4, + To: 4, + NewLines: []string{ + "", + "// add line 1", + "", + }, + }, + { + From: 7, + To: 7, + NewLines: []string{ + " Errorf(format string, args ...interface{})", + " // add line 2", + }, + }, }, }, - }) -} - -func TestExtractChangesFromHunkAddOnlyInAllDiff(t *testing.T) { - const diff = `diff --git a/test.go b/test.go -new file mode 100644 -index 0000000..6399915 ---- /dev/null -+++ b/test.go -@@ -0,0 +1,3 @@ -+package test -+ -+// line -` - - log := logutils.NewMockLog(). - OnInfof("The diff contains only additions: no original or deleted lines: %#v", mock.Anything) - - var noChanges []Change - testDiffProducesChanges(t, log, diff, noChanges...) -} - -func TestExtractChangesFromHunkAddOnlyMultipleLines(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..3b83a94 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -2,6 +2,9 @@ package logutil - - type Func func(format string, args ...interface{}) - -+// add line 1 -+// add line 2 -+ - type Log interface { - Fatalf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) -` - - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 4, - To: 4, - }, - Replacement: result.Replacement{ - NewLines: []string{ - "", - "// add line 1", - "// add line 2", - "", - }, + { + diff: "add_only_in_all_diff.diff", + log: logutils.NewMockLog(). + OnInfof("The diff contains only additions: no original or deleted lines: %#v", mock.Anything), }, - }) -} - -func TestExtractChangesFromHunkAddOnlyDifferentLines(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..e5ed2ad 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -2,9 +2,12 @@ package logutil - - type Func func(format string, args ...interface{}) - -+// add line 1 -+ - type Log interface { - Fatalf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) -+ // add line 2 - Warnf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Debugf(key string, format string, args ...interface{}) -` - - expectedChanges := []Change{ { - LineRange: result.Range{ + diff: "add_only_multiple_lines.diff", + expected: []Change{{ From: 4, To: 4, - }, - Replacement: result.Replacement{ NewLines: []string{ "", "// add line 1", + "// add line 2", "", }, - }, + }}, }, { - LineRange: result.Range{ - From: 7, - To: 7, - }, - Replacement: result.Replacement{ + diff: "add_only_on_first_line.diff", + expected: []Change{{ + From: 1, + To: 1, NewLines: []string{ - " Errorf(format string, args ...interface{})", - " // add line 2", + "// added line", + "package logutil", }, - }, + }}, }, - } - - testDiffProducesChanges(t, nil, diff, expectedChanges...) -} - -func TestExtractChangesDeleteOnlyFirstLines(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..0fb554e 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -1,5 +1,3 @@ --package logutil -- - type Func func(format string, args ...interface{}) - - type Log interface { -` - - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 1, - To: 2, + { + diff: "add_only_on_first_line_with_shared_original_line.diff", + expected: []Change{{ + From: 1, + To: 1, + NewLines: []string{ + "// added line 1", + "package logutil", + "// added line 2", + "// added line 3", + }, + }}, }, - Replacement: result.Replacement{ - NeedOnlyDelete: true, + { + diff: "replace_line.diff", + expected: []Change{{ + From: 1, + To: 1, + NewLines: []string{"package test2"}, + }}, }, - }) -} - -func TestExtractChangesReplaceLine(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..c2a8516 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -1,4 +1,4 @@ --package logutil -+package test2 - - type Func func(format string, args ...interface{}) -` - - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 1, - To: 1, + { + diff: "replace_line_after_first_line_adding.diff", + expected: []Change{ + { + From: 1, + To: 1, + NewLines: []string{ + "// added line", + "package logutil", + }, + }, + { + From: 3, + To: 3, + NewLines: []string{ + "// changed line", + }, + }, + }, }, - Replacement: result.Replacement{ - NewLines: []string{"package test2"}, + { + diff: "gofmt_diff.diff", + expected: []Change{ + { + From: 4, + To: 6, + NewLines: []string{ + "func gofmt(a, b int) int {", + " if a != b {", + " return 1", + }, + }, + { + From: 8, + To: 8, + NewLines: []string{ + " return 2", + }, + }, + }, }, - }) -} + } -func TestExtractChangesReplaceLineAfterFirstLineAdding(t *testing.T) { - const diff = `diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go -index 258b340..43fc0de 100644 ---- a/internal/shared/logutil/log.go -+++ b/internal/shared/logutil/log.go -@@ -1,6 +1,7 @@ -+// added line - package logutil + for _, test := range testCases { + t.Run(test.diff, func(t *testing.T) { + t.Parallel() --type Func func(format string, args ...interface{}) -+// changed line + diff, err := os.ReadFile(filepath.Join("testdata", test.diff)) + require.NoError(t, err) - type Log interface { - Fatalf(format string, args ...interface{})` + diffs, err := diffpkg.ParseMultiFileDiff(diff) + if err != nil { + require.NoError(t, err) + } - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 1, - To: 1, - }, - Replacement: result.Replacement{ - NewLines: []string{ - "// added line", - "package logutil", - }, - }, - }, Change{ - LineRange: result.Range{ - From: 3, - To: 3, - }, - Replacement: result.Replacement{ - NewLines: []string{ - "// changed line", - }, - }, - }) -} + require.Len(t, diffs, 1) -func TestGofmtDiff(t *testing.T) { - const diff = `diff --git a/gofmt.go b/gofmt.go -index 2c9f78d..c0d5791 100644 ---- a/gofmt.go -+++ b/gofmt.go -@@ -1,9 +1,9 @@ - //golangcitest:args -Egofmt - package p + hunks := diffs[0].Hunks + assert.NotEmpty(t, hunks) -- func gofmt(a, b int) int { -- if a != b { -- return 1 -+func gofmt(a, b int) int { -+ if a != b { -+ return 1 - } -- return 2 -+ return 2 - } -` - testDiffProducesChanges(t, nil, diff, Change{ - LineRange: result.Range{ - From: 4, - To: 6, - }, - Replacement: result.Replacement{ - NewLines: []string{ - "func gofmt(a, b int) int {", - " if a != b {", - " return 1", - }, - }, - }, Change{ - LineRange: result.Range{ - From: 8, - To: 8, - }, - Replacement: result.Replacement{ - NewLines: []string{ - " return 2", - }, - }, - }) + var changes []Change + for _, hunk := range hunks { + p := hunkChangesParser{log: test.log} + + changes = append(changes, p.parse(hunk)...) + } + + assert.Equal(t, test.expected, changes) + }) + } } diff --git a/pkg/golinters/internal/testdata/add_only.diff b/pkg/golinters/internal/testdata/add_only.diff new file mode 100644 index 000000000000..a82a4023be8b --- /dev/null +++ b/pkg/golinters/internal/testdata/add_only.diff @@ -0,0 +1,11 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..43d04bf 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -1,5 +1,6 @@ + package logutil + ++// added line + type Func func(format string, args ...interface{}) + + type Log interface { diff --git a/pkg/golinters/internal/testdata/add_only_different_lines.diff b/pkg/golinters/internal/testdata/add_only_different_lines.diff new file mode 100644 index 000000000000..aa2233f645dd --- /dev/null +++ b/pkg/golinters/internal/testdata/add_only_different_lines.diff @@ -0,0 +1,17 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..e5ed2ad 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -2,9 +2,12 @@ package logutil + + type Func func(format string, args ...interface{}) + ++// add line 1 ++ + type Log interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) ++ // add line 2 + Warnf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Debugf(key string, format string, args ...interface{}) diff --git a/pkg/golinters/internal/testdata/add_only_in_all_diff.diff b/pkg/golinters/internal/testdata/add_only_in_all_diff.diff new file mode 100644 index 000000000000..c32569ceb2b8 --- /dev/null +++ b/pkg/golinters/internal/testdata/add_only_in_all_diff.diff @@ -0,0 +1,9 @@ +diff --git a/test.go b/test.go +new file mode 100644 +index 0000000..6399915 +--- /dev/null ++++ b/test.go +@@ -0,0 +1,3 @@ ++package test ++ ++// line diff --git a/pkg/golinters/internal/testdata/add_only_multiple_lines.diff b/pkg/golinters/internal/testdata/add_only_multiple_lines.diff new file mode 100644 index 000000000000..c976b7422fe0 --- /dev/null +++ b/pkg/golinters/internal/testdata/add_only_multiple_lines.diff @@ -0,0 +1,14 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..3b83a94 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -2,6 +2,9 @@ package logutil + + type Func func(format string, args ...interface{}) + ++// add line 1 ++// add line 2 ++ + type Log interface { + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) diff --git a/pkg/golinters/internal/testdata/add_only_on_first_line.diff b/pkg/golinters/internal/testdata/add_only_on_first_line.diff new file mode 100644 index 000000000000..d91d315bf446 --- /dev/null +++ b/pkg/golinters/internal/testdata/add_only_on_first_line.diff @@ -0,0 +1,9 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..97e6660 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -1,3 +1,4 @@ ++// added line + package logutil + + type Func func(format string, args ...interface{}) diff --git a/pkg/golinters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff b/pkg/golinters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff new file mode 100644 index 000000000000..9d1ce94b6e4c --- /dev/null +++ b/pkg/golinters/internal/testdata/add_only_on_first_line_with_shared_original_line.diff @@ -0,0 +1,11 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..7ff80c9 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -1,4 +1,7 @@ ++// added line 1 + package logutil ++// added line 2 ++// added line 3 + + type Func func(format string, args ...interface{}) diff --git a/pkg/golinters/internal/testdata/delete_only_first_lines.diff b/pkg/golinters/internal/testdata/delete_only_first_lines.diff new file mode 100644 index 000000000000..a6e16c3f3418 --- /dev/null +++ b/pkg/golinters/internal/testdata/delete_only_first_lines.diff @@ -0,0 +1,10 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..0fb554e 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -1,5 +1,3 @@ +-package logutil +- + type Func func(format string, args ...interface{}) + + type Log interface { diff --git a/pkg/golinters/internal/testdata/gofmt_diff.diff b/pkg/golinters/internal/testdata/gofmt_diff.diff new file mode 100644 index 000000000000..a237c92ec402 --- /dev/null +++ b/pkg/golinters/internal/testdata/gofmt_diff.diff @@ -0,0 +1,18 @@ +diff --git a/gofmt.go b/gofmt.go +index 2c9f78d..c0d5791 100644 +--- a/gofmt.go ++++ b/gofmt.go +@@ -1,9 +1,9 @@ + //golangcitest:args -Egofmt + package p + +- func gofmt(a, b int) int { +- if a != b { +- return 1 ++func gofmt(a, b int) int { ++ if a != b { ++ return 1 + } +- return 2 ++ return 2 + } diff --git a/pkg/golinters/internal/testdata/replace_line.diff b/pkg/golinters/internal/testdata/replace_line.diff new file mode 100644 index 000000000000..d06d1867ef4c --- /dev/null +++ b/pkg/golinters/internal/testdata/replace_line.diff @@ -0,0 +1,9 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..c2a8516 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -1,4 +1,4 @@ +-package logutil ++package test2 + + type Func func(format string, args ...interface{}) diff --git a/pkg/golinters/internal/testdata/replace_line_after_first_line_adding.diff b/pkg/golinters/internal/testdata/replace_line_after_first_line_adding.diff new file mode 100644 index 000000000000..92e30af57e19 --- /dev/null +++ b/pkg/golinters/internal/testdata/replace_line_after_first_line_adding.diff @@ -0,0 +1,13 @@ +diff --git a/internal/shared/logutil/log.go b/internal/shared/logutil/log.go +index 258b340..43fc0de 100644 +--- a/internal/shared/logutil/log.go ++++ b/internal/shared/logutil/log.go +@@ -1,6 +1,7 @@ ++// added line + package logutil + +-type Func func(format string, args ...interface{}) ++// changed line + + type Log interface { + Fatalf(format string, args ...interface{}) \ No newline at end of file From e7c65cb8595aa99963bf96cf9823dc991c5ea6db Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 12 Dec 2024 17:56:25 +0100 Subject: [PATCH 13/57] chore: refactor nolintlint --- pkg/golinters/nolintlint/internal/issues.go | 41 +++ .../nolintlint/internal/nolintlint.go | 229 ++++++---------- .../nolintlint/internal/nolintlint_test.go | 244 ++++++++++-------- pkg/golinters/nolintlint/nolintlint.go | 84 ++---- .../nolintlint/nolintlint_integration_test.go | 19 ++ .../nolintlint}/testdata/fix/in/nolintlint.go | 0 .../testdata/fix/out/nolintlint.go | 0 .../nolintlint}/testdata/nolintlint.go | 2 +- .../nolintlint/testdata}/nolintlint.yml | 0 .../nolintlint}/testdata/nolintlint_unused.go | 2 +- .../testdata}/nolintlint_unused.yml | 0 11 files changed, 305 insertions(+), 316 deletions(-) create mode 100644 pkg/golinters/nolintlint/internal/issues.go create mode 100644 pkg/golinters/nolintlint/nolintlint_integration_test.go rename {test => pkg/golinters/nolintlint}/testdata/fix/in/nolintlint.go (100%) rename {test => pkg/golinters/nolintlint}/testdata/fix/out/nolintlint.go (100%) rename {test => pkg/golinters/nolintlint}/testdata/nolintlint.go (91%) rename {test/testdata/configs => pkg/golinters/nolintlint/testdata}/nolintlint.yml (100%) rename {test => pkg/golinters/nolintlint}/testdata/nolintlint_unused.go (86%) rename {test/testdata/configs => pkg/golinters/nolintlint/testdata}/nolintlint_unused.yml (100%) diff --git a/pkg/golinters/nolintlint/internal/issues.go b/pkg/golinters/nolintlint/internal/issues.go new file mode 100644 index 000000000000..5e9ba4117cad --- /dev/null +++ b/pkg/golinters/nolintlint/internal/issues.go @@ -0,0 +1,41 @@ +package internal + +import ( + "fmt" + "strings" + "unicode" +) + +func formatExtraLeadingSpace(fullDirective string) string { + return fmt.Sprintf("directive `%s` should not have more than one leading space", fullDirective) +} + +func formatNotMachine(fullDirective string) string { + expected := fullDirective[:2] + strings.TrimLeftFunc(fullDirective[2:], unicode.IsSpace) + return fmt.Sprintf("directive `%s` should be written without leading space as `%s`", + fullDirective, expected) +} + +func formatNotSpecific(fullDirective, directiveWithOptionalLeadingSpace string) string { + return fmt.Sprintf("directive `%s` should mention specific linter such as `%s:my-linter`", + fullDirective, directiveWithOptionalLeadingSpace) +} + +func formatParseError(fullDirective, directiveWithOptionalLeadingSpace string) string { + return fmt.Sprintf("directive `%s` should match `%s[:] [// ]`", + fullDirective, + directiveWithOptionalLeadingSpace) +} + +func formatNoExplanation(fullDirective, fullDirectiveWithoutExplanation string) string { + return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`", + fullDirective, fullDirectiveWithoutExplanation) +} + +func formatUnusedCandidate(fullDirective, expectedLinter string) string { + details := fmt.Sprintf("directive `%s` is unused", fullDirective) + if expectedLinter != "" { + details += fmt.Sprintf(" for linter %q", expectedLinter) + } + return details +} diff --git a/pkg/golinters/nolintlint/internal/nolintlint.go b/pkg/golinters/nolintlint/internal/nolintlint.go index 08dd743783c3..610682a9baa4 100644 --- a/pkg/golinters/nolintlint/internal/nolintlint.go +++ b/pkg/golinters/nolintlint/internal/nolintlint.go @@ -2,123 +2,16 @@ package internal import ( - "fmt" - "go/ast" - "go/token" "regexp" "strings" - "unicode" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/goanalysis" "github.com/golangci/golangci-lint/pkg/result" ) -type BaseIssue struct { - fullDirective string - directiveWithOptionalLeadingSpace string - position token.Position - replacement *result.Replacement -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (b BaseIssue) Position() token.Position { - return b.position -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (b BaseIssue) Replacement() *result.Replacement { - return b.replacement -} - -type ExtraLeadingSpace struct { - BaseIssue -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (i ExtraLeadingSpace) Details() string { - return fmt.Sprintf("directive `%s` should not have more than one leading space", i.fullDirective) -} - -func (i ExtraLeadingSpace) String() string { return toString(i) } - -type NotMachine struct { - BaseIssue -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (i NotMachine) Details() string { - expected := i.fullDirective[:2] + strings.TrimLeftFunc(i.fullDirective[2:], unicode.IsSpace) - return fmt.Sprintf("directive `%s` should be written without leading space as `%s`", - i.fullDirective, expected) -} - -func (i NotMachine) String() string { return toString(i) } - -type NotSpecific struct { - BaseIssue -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (i NotSpecific) Details() string { - return fmt.Sprintf("directive `%s` should mention specific linter such as `%s:my-linter`", - i.fullDirective, i.directiveWithOptionalLeadingSpace) -} - -func (i NotSpecific) String() string { return toString(i) } - -type ParseError struct { - BaseIssue -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (i ParseError) Details() string { - return fmt.Sprintf("directive `%s` should match `%s[:] [// ]`", - i.fullDirective, - i.directiveWithOptionalLeadingSpace) -} - -func (i ParseError) String() string { return toString(i) } - -type NoExplanation struct { - BaseIssue - fullDirectiveWithoutExplanation string -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (i NoExplanation) Details() string { - return fmt.Sprintf("directive `%s` should provide explanation such as `%s // this is why`", - i.fullDirective, i.fullDirectiveWithoutExplanation) -} - -func (i NoExplanation) String() string { return toString(i) } - -type UnusedCandidate struct { - BaseIssue - ExpectedLinter string -} - -//nolint:gocritic // TODO(ldez) must be change in the future. -func (i UnusedCandidate) Details() string { - details := fmt.Sprintf("directive `%s` is unused", i.fullDirective) - if i.ExpectedLinter != "" { - details += fmt.Sprintf(" for linter %q", i.ExpectedLinter) - } - return details -} - -func (i UnusedCandidate) String() string { return toString(i) } - -func toString(issue Issue) string { - return fmt.Sprintf("%s at %s", issue.Details(), issue.Position()) -} - -type Issue interface { - Details() string - Position() token.Position - String() string - Replacement() *result.Replacement -} - -type Needs uint +const LinterName = "nolintlint" const ( NeedsMachineOnly Needs = 1 << iota @@ -128,6 +21,8 @@ const ( NeedsAll = NeedsMachineOnly | NeedsSpecific | NeedsExplanation ) +type Needs uint + var commentPattern = regexp.MustCompile(`^//\s*(nolint)(:\s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\b`) // matches a complete nolint directive @@ -157,15 +52,10 @@ var ( ) //nolint:funlen,gocyclo // the function is going to be refactored in the future -func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { - var issues []Issue - - for _, node := range nodes { - file, ok := node.(*ast.File) - if !ok { - continue - } +func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { + var issues []goanalysis.Issue + for _, file := range pass.Files { for _, c := range file.Comments { for _, comment := range c.List { if !commentPattern.MatchString(comment.Text) { @@ -188,39 +78,51 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { split := strings.Split(strings.SplitN(comment.Text, ":", 2)[0], "//") directiveWithOptionalLeadingSpace += strings.TrimSpace(split[1]) - pos := fset.Position(comment.Pos()) - end := fset.Position(comment.End()) - - base := BaseIssue{ - fullDirective: comment.Text, - directiveWithOptionalLeadingSpace: directiveWithOptionalLeadingSpace, - position: pos, - } + pos := pass.Fset.Position(comment.Pos()) + end := pass.Fset.Position(comment.End()) // check for, report and eliminate leading spaces, so we can check for other issues if leadingSpace != "" { removeWhitespace := &result.Replacement{ Inline: &result.InlineFix{ - StartCol: pos.Column + 1, - Length: len(leadingSpace), - NewString: "", + StartCol: pos.Column + 1, + Length: len(leadingSpace), }, } + if (l.needs & NeedsMachineOnly) != 0 { - issue := NotMachine{BaseIssue: base} - issue.BaseIssue.replacement = removeWhitespace - issues = append(issues, issue) + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatNotMachine(comment.Text), + Pos: pos, + Replacement: removeWhitespace, + } + + issues = append(issues, goanalysis.NewIssue(issue, pass)) } else if len(leadingSpace) > 1 { - issue := ExtraLeadingSpace{BaseIssue: base} - issue.BaseIssue.replacement = removeWhitespace - issue.BaseIssue.replacement.Inline.NewString = " " // assume a single space was intended - issues = append(issues, issue) + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatExtraLeadingSpace(comment.Text), + Pos: pos, + Replacement: removeWhitespace, + } + + issue.Replacement.Inline.NewString = " " // assume a single space was intended + + issues = append(issues, goanalysis.NewIssue(issue, pass)) } } fullMatches := fullDirectivePattern.FindStringSubmatch(comment.Text) if len(fullMatches) == 0 { - issues = append(issues, ParseError{BaseIssue: base}) + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatParseError(comment.Text, directiveWithOptionalLeadingSpace), + Pos: pos, + } + + issues = append(issues, goanalysis.NewIssue(issue, pass)) + continue } @@ -246,7 +148,13 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { if (l.needs & NeedsSpecific) != 0 { if len(linters) == 0 { - issues = append(issues, NotSpecific{BaseIssue: base}) + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatNotSpecific(comment.Text, directiveWithOptionalLeadingSpace), + Pos: pos, + } + + issues = append(issues, goanalysis.NewIssue(issue, pass)) } } @@ -261,26 +169,39 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { removeNolintCompletely.NeedOnlyDelete = true } else { removeNolintCompletely.Inline = &result.InlineFix{ - StartCol: startCol, - Length: end.Column - pos.Column, - NewString: "", + StartCol: startCol, + Length: end.Column - pos.Column, } } if len(linters) == 0 { - issue := UnusedCandidate{BaseIssue: base} - issue.replacement = removeNolintCompletely - issues = append(issues, issue) + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatUnusedCandidate(comment.Text, ""), + Pos: pos, + ExpectNoLint: true, + Replacement: removeNolintCompletely, + } + + issues = append(issues, goanalysis.NewIssue(issue, pass)) } else { for _, linter := range linters { - issue := UnusedCandidate{BaseIssue: base, ExpectedLinter: linter} + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatUnusedCandidate(comment.Text, linter), + Pos: pos, + ExpectNoLint: true, + ExpectedNoLintLinter: linter, + } + // only offer replacement if there is a single linter // because of issues around commas and the possibility of all // linters being removed if len(linters) == 1 { - issue.replacement = removeNolintCompletely + issue.Replacement = removeNolintCompletely } - issues = append(issues, issue) + + issues = append(issues, goanalysis.NewIssue(issue, pass)) } } } @@ -297,10 +218,14 @@ func (l Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { if needsExplanation { fullDirectiveWithoutExplanation := trailingBlankExplanation.ReplaceAllString(comment.Text, "") - issues = append(issues, NoExplanation{ - BaseIssue: base, - fullDirectiveWithoutExplanation: fullDirectiveWithoutExplanation, - }) + + issue := &result.Issue{ + FromLinter: LinterName, + Text: formatNoExplanation(comment.Text, fullDirectiveWithoutExplanation), + Pos: pos, + } + + issues = append(issues, goanalysis.NewIssue(issue, pass)) } } } diff --git a/pkg/golinters/nolintlint/internal/nolintlint_test.go b/pkg/golinters/nolintlint/internal/nolintlint_test.go index ddc5bde7b1ea..05a8f7bb7a22 100644 --- a/pkg/golinters/nolintlint/internal/nolintlint_test.go +++ b/pkg/golinters/nolintlint/internal/nolintlint_test.go @@ -1,33 +1,30 @@ package internal import ( + "go/ast" "go/parser" "go/token" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/result" ) func TestLinter_Run(t *testing.T) { - type issueWithReplacement struct { - issue string - replacement *result.Replacement - } testCases := []struct { desc string needs Needs excludes []string contents string - expected []issueWithReplacement + expected []result.Issue }{ { desc: "when no explanation is provided", needs: NeedsExplanation, - contents: ` -package bar + contents: `package bar // example //nolint @@ -37,191 +34,233 @@ func foo() { bad() //nolint // good() //nolint // this is ok other() //nolintother -}`, - expected: []issueWithReplacement{ - {issue: "directive `//nolint` should provide explanation such as `//nolint // this is why` at testing.go:5:1"}, - {issue: "directive `//nolint` should provide explanation such as `//nolint // this is why` at testing.go:7:9"}, - {issue: "directive `//nolint //` should provide explanation such as `//nolint // this is why` at testing.go:8:9"}, - {issue: "directive `//nolint // ` should provide explanation such as `//nolint // this is why` at testing.go:9:9"}, +} +`, + expected: []result.Issue{ + { + FromLinter: "nolintlint", + Text: "directive `//nolint` should provide explanation such as `//nolint // this is why`", + Pos: token.Position{Filename: "testing.go", Offset: 24, Line: 4, Column: 1}, + }, + { + FromLinter: "nolintlint", + Text: "directive `//nolint` should provide explanation such as `//nolint // this is why`", + Pos: token.Position{Filename: "testing.go", Offset: 54, Line: 6, Column: 9}, + }, + { + FromLinter: "nolintlint", + Text: "directive `//nolint //` should provide explanation such as `//nolint // this is why`", + Pos: token.Position{Filename: "testing.go", Offset: 71, Line: 7, Column: 9}, + }, + { + FromLinter: "nolintlint", + Text: "directive `//nolint // ` should provide explanation such as `//nolint // this is why`", + Pos: token.Position{Filename: "testing.go", Offset: 91, Line: 8, Column: 9}, + }, }, }, { desc: "when multiple directives on multiple lines", needs: NeedsExplanation, - contents: ` -package bar + contents: `package bar // example //nolint // this is ok //nolint:dupl -func foo() {}`, - expected: []issueWithReplacement{ - {issue: "directive `//nolint:dupl` should provide explanation such as `//nolint:dupl // this is why` at testing.go:6:1"}, - }, +func foo() {} +`, + expected: []result.Issue{{ + FromLinter: "nolintlint", + Text: "directive `//nolint:dupl` should provide explanation such as `//nolint:dupl // this is why`", + Pos: token.Position{Filename: "testing.go", Offset: 47, Line: 5, Column: 1}, + }}, }, { desc: "when no explanation is needed for a specific linter", needs: NeedsExplanation, excludes: []string{"lll"}, - contents: ` -package bar + contents: `package bar func foo() { thisIsAReallyLongLine() //nolint:lll -}`, +} +`, }, { desc: "when no specific linter is mentioned", needs: NeedsSpecific, - contents: ` -package bar + contents: `package bar func foo() { good() //nolint:my-linter bad() //nolint bad() //nolint // because -}`, - expected: []issueWithReplacement{ - {issue: "directive `//nolint` should mention specific linter such as `//nolint:my-linter` at testing.go:6:9"}, - {issue: "directive `//nolint // because` should mention specific linter such as `//nolint:my-linter` at testing.go:7:9"}, +} +`, + expected: []result.Issue{ + { + FromLinter: "nolintlint", + Text: "directive `//nolint` should mention specific linter such as `//nolint:my-linter`", + Pos: token.Position{Filename: "testing.go", Offset: 62, Line: 5, Column: 9}, + }, + { + FromLinter: "nolintlint", + Text: "directive `//nolint // because` should mention specific linter such as `//nolint:my-linter`", + Pos: token.Position{Filename: "testing.go", Offset: 79, Line: 6, Column: 9}, + }, }, }, { desc: "when machine-readable style isn't used", - contents: ` -package bar + contents: `package bar func foo() { bad() // nolint bad() // nolint good() //nolint -}`, - expected: []issueWithReplacement{ +} +`, + expected: []result.Issue{ { - issue: "directive `// nolint` should be written without leading space as `//nolint` at testing.go:5:9", - replacement: &result.Replacement{ + FromLinter: "nolintlint", + Text: "directive `// nolint` should be written without leading space as `//nolint`", + Replacement: &result.Replacement{ Inline: &result.InlineFix{ - StartCol: 10, - Length: 1, - NewString: "", + StartCol: 10, + Length: 1, }, }, + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, }, { - issue: "directive `// nolint` should be written without leading space as `//nolint` at testing.go:6:9", - replacement: &result.Replacement{ + FromLinter: "nolintlint", + Text: "directive `// nolint` should be written without leading space as `//nolint`", + Replacement: &result.Replacement{ Inline: &result.InlineFix{ - StartCol: 10, - Length: 3, - NewString: "", + StartCol: 10, + Length: 3, }, }, + Pos: token.Position{Filename: "testing.go", Offset: 52, Line: 5, Column: 9}, }, }, }, { desc: "spaces are allowed in comma-separated list of linters", - contents: ` -package bar + contents: `package bar func foo() { good() //nolint:linter1,linter-two bad() //nolint:linter1 linter2 good() //nolint: linter1,linter2 good() //nolint: linter1, linter2 -}`, - expected: []issueWithReplacement{ - {issue: "directive `//nolint:linter1 linter2` should match `//nolint[:] [// ]` at testing.go:6:9"}, - }, +} +`, + expected: []result.Issue{{ + FromLinter: "nolintlint", + Text: "directive `//nolint:linter1 linter2` should match `//nolint[:] [// ]`", + Pos: token.Position{Filename: "testing.go", Offset: 71, Line: 5, Column: 9}, + }}, }, { desc: "multi-line comments don't confuse parser", - contents: ` -package bar + contents: `package bar func foo() { //nolint:test // something else -}`, +} +`, }, { desc: "needs unused without specific linter generates replacement", needs: NeedsUnused, - contents: ` -package bar + contents: `package bar func foo() { bad() //nolint -}`, - expected: []issueWithReplacement{ - { - issue: "directive `//nolint` is unused at testing.go:5:9", - replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 8, - Length: 8, - NewString: "", - }, +} +`, + expected: []result.Issue{{ + FromLinter: "nolintlint", + Text: "directive `//nolint` is unused", + Replacement: &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: 8, + Length: 8, }, }, - }, + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + ExpectNoLint: true, + }}, }, { desc: "needs unused with one specific linter generates replacement", needs: NeedsUnused, - contents: ` -package bar + contents: `package bar func foo() { bad() //nolint:somelinter -}`, - expected: []issueWithReplacement{ - { - issue: "directive `//nolint:somelinter` is unused for linter \"somelinter\" at testing.go:5:9", - replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 8, - Length: 19, - NewString: "", - }, +} +`, + expected: []result.Issue{{ + FromLinter: "nolintlint", + Text: "directive `//nolint:somelinter` is unused for linter \"somelinter\"", + Replacement: &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: 8, + Length: 19, }, }, - }, + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + ExpectNoLint: true, + ExpectedNoLintLinter: "somelinter", + }}, }, { desc: "needs unused with one specific linter in a new line generates replacement", needs: NeedsUnused, - contents: ` -package bar + contents: `package bar //nolint:somelinter func foo() { bad() -}`, - expected: []issueWithReplacement{ - { - issue: "directive `//nolint:somelinter` is unused for linter \"somelinter\" at testing.go:4:1", - replacement: &result.Replacement{ - NeedOnlyDelete: true, - }, +} +`, + expected: []result.Issue{{ + FromLinter: "nolintlint", + Text: "directive `//nolint:somelinter` is unused for linter \"somelinter\"", + Replacement: &result.Replacement{ + NeedOnlyDelete: true, }, - }, + Pos: token.Position{Filename: "testing.go", Offset: 13, Line: 3, Column: 1}, + ExpectNoLint: true, + ExpectedNoLintLinter: "somelinter", + }}, }, { desc: "needs unused with multiple specific linters does not generate replacements", needs: NeedsUnused, - contents: ` -package bar + contents: `package bar func foo() { bad() //nolint:linter1,linter2 -}`, - expected: []issueWithReplacement{ +} +`, + expected: []result.Issue{ { - issue: "directive `//nolint:linter1,linter2` is unused for linter \"linter1\" at testing.go:5:9", + FromLinter: "nolintlint", + Text: "directive `//nolint:linter1,linter2` is unused for linter \"linter1\"", + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + ExpectNoLint: true, + ExpectedNoLintLinter: "linter1", }, { - issue: "directive `//nolint:linter1,linter2` is unused for linter \"linter2\" at testing.go:5:9", + FromLinter: "nolintlint", + Text: "directive `//nolint:linter1,linter2` is unused for linter \"linter2\"", + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + ExpectNoLint: true, + ExpectedNoLintLinter: "linter2", }, }, }, @@ -237,19 +276,20 @@ func foo() { expr, err := parser.ParseFile(fset, "testing.go", test.contents, parser.ParseComments) require.NoError(t, err) - actualIssues, err := linter.Run(fset, expr) + pass := &analysis.Pass{ + Fset: fset, + Files: []*ast.File{expr}, + } + + analysisIssues, err := linter.Run(pass) require.NoError(t, err) - actualIssuesWithReplacements := make([]issueWithReplacement, 0, len(actualIssues)) - for _, i := range actualIssues { - actualIssuesWithReplacements = append(actualIssuesWithReplacements, issueWithReplacement{ - issue: i.String(), - replacement: i.Replacement(), - }) + var issues []result.Issue + for _, i := range analysisIssues { + issues = append(issues, i.Issue) } - assert.ElementsMatch(t, test.expected, actualIssuesWithReplacements, - "expected %s \nbut got %s", test.expected, actualIssuesWithReplacements) + assert.Equal(t, test.expected, issues) }) } } diff --git a/pkg/golinters/nolintlint/nolintlint.go b/pkg/golinters/nolintlint/nolintlint.go index 9f04454a5a2b..75933f7c3c52 100644 --- a/pkg/golinters/nolintlint/nolintlint.go +++ b/pkg/golinters/nolintlint/nolintlint.go @@ -2,31 +2,47 @@ package nolintlint import ( "fmt" - "go/ast" "sync" "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/golinters/internal" + "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/golinters/nolintlint/internal" + nolintlint "github.com/golangci/golangci-lint/pkg/golinters/nolintlint/internal" "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) -const LinterName = "nolintlint" +const LinterName = nolintlint.LinterName func New(settings *config.NoLintLintSettings) *goanalysis.Linter { var mu sync.Mutex var resIssues []goanalysis.Issue + var needs nolintlint.Needs + if settings.RequireExplanation { + needs |= nolintlint.NeedsExplanation + } + if settings.RequireSpecific { + needs |= nolintlint.NeedsSpecific + } + if !settings.AllowUnused { + needs |= nolintlint.NeedsUnused + } + + lnt, err := nolintlint.NewLinter(needs, settings.AllowNoExplanation) + if err != nil { + internal.LinterLogger.Fatalf("asasalint: create analyzer: %v", err) + } + analyzer := &analysis.Analyzer{ - Name: LinterName, + Name: nolintlint.LinterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runNoLintLint(pass, settings) + issues, err := lnt.Run(pass) if err != nil { - return nil, err + return nil, fmt.Errorf("linter failed to run: %w", err) } if len(issues) == 0 { @@ -42,7 +58,7 @@ func New(settings *config.NoLintLintSettings) *goanalysis.Linter { } return goanalysis.NewLinter( - LinterName, + nolintlint.LinterName, "Reports ill-formed or insufficient nolint directives", []*analysis.Analyzer{analyzer}, nil, @@ -50,55 +66,3 @@ func New(settings *config.NoLintLintSettings) *goanalysis.Linter { return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } - -func runNoLintLint(pass *analysis.Pass, settings *config.NoLintLintSettings) ([]goanalysis.Issue, error) { - var needs internal.Needs - if settings.RequireExplanation { - needs |= internal.NeedsExplanation - } - if settings.RequireSpecific { - needs |= internal.NeedsSpecific - } - if !settings.AllowUnused { - needs |= internal.NeedsUnused - } - - lnt, err := internal.NewLinter(needs, settings.AllowNoExplanation) - if err != nil { - return nil, err - } - - nodes := make([]ast.Node, 0, len(pass.Files)) - for _, n := range pass.Files { - nodes = append(nodes, n) - } - - lintIssues, err := lnt.Run(pass.Fset, nodes...) - if err != nil { - return nil, fmt.Errorf("linter failed to run: %w", err) - } - - var issues []goanalysis.Issue - - for _, i := range lintIssues { - expectNoLint := false - var expectedNolintLinter string - if ii, ok := i.(internal.UnusedCandidate); ok { - expectedNolintLinter = ii.ExpectedLinter - expectNoLint = true - } - - issue := &result.Issue{ - FromLinter: LinterName, - Text: i.Details(), - Pos: i.Position(), - ExpectNoLint: expectNoLint, - ExpectedNoLintLinter: expectedNolintLinter, - Replacement: i.Replacement(), - } - - issues = append(issues, goanalysis.NewIssue(issue, pass)) - } - - return issues, nil -} diff --git a/pkg/golinters/nolintlint/nolintlint_integration_test.go b/pkg/golinters/nolintlint/nolintlint_integration_test.go new file mode 100644 index 000000000000..6c175d529a67 --- /dev/null +++ b/pkg/golinters/nolintlint/nolintlint_integration_test.go @@ -0,0 +1,19 @@ +package nolintlint + +import ( + "testing" + + "github.com/golangci/golangci-lint/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/test/testdata/fix/in/nolintlint.go b/pkg/golinters/nolintlint/testdata/fix/in/nolintlint.go similarity index 100% rename from test/testdata/fix/in/nolintlint.go rename to pkg/golinters/nolintlint/testdata/fix/in/nolintlint.go diff --git a/test/testdata/fix/out/nolintlint.go b/pkg/golinters/nolintlint/testdata/fix/out/nolintlint.go similarity index 100% rename from test/testdata/fix/out/nolintlint.go rename to pkg/golinters/nolintlint/testdata/fix/out/nolintlint.go diff --git a/test/testdata/nolintlint.go b/pkg/golinters/nolintlint/testdata/nolintlint.go similarity index 91% rename from test/testdata/nolintlint.go rename to pkg/golinters/nolintlint/testdata/nolintlint.go index adb7bbd50a03..0e380342d5f1 100644 --- a/test/testdata/nolintlint.go +++ b/pkg/golinters/nolintlint/testdata/nolintlint.go @@ -1,6 +1,6 @@ //golangcitest:args -Enolintlint -Emisspell //golangcitest:expected_linter nolintlint -//golangcitest:config_path testdata/configs/nolintlint.yml +//golangcitest:config_path nolintlint.yml package testdata import "fmt" diff --git a/test/testdata/configs/nolintlint.yml b/pkg/golinters/nolintlint/testdata/nolintlint.yml similarity index 100% rename from test/testdata/configs/nolintlint.yml rename to pkg/golinters/nolintlint/testdata/nolintlint.yml diff --git a/test/testdata/nolintlint_unused.go b/pkg/golinters/nolintlint/testdata/nolintlint_unused.go similarity index 86% rename from test/testdata/nolintlint_unused.go rename to pkg/golinters/nolintlint/testdata/nolintlint_unused.go index 285d341edf09..6984f190d394 100644 --- a/test/testdata/nolintlint_unused.go +++ b/pkg/golinters/nolintlint/testdata/nolintlint_unused.go @@ -1,6 +1,6 @@ //golangcitest:args -Enolintlint -Evarcheck //golangcitest:expected_linter nolintlint -//golangcitest:config_path testdata/configs/nolintlint_unused.yml +//golangcitest:config_path nolintlint_unused.yml package testdata import "fmt" diff --git a/test/testdata/configs/nolintlint_unused.yml b/pkg/golinters/nolintlint/testdata/nolintlint_unused.yml similarity index 100% rename from test/testdata/configs/nolintlint_unused.yml rename to pkg/golinters/nolintlint/testdata/nolintlint_unused.yml From bf20023d2259e365c1b9893105393d63879a113d Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:28:30 +0100 Subject: [PATCH 14/57] chore: copy x/tools/internal/diff --- internal/x/tools/diff/diff.go | 176 +++++++++ internal/x/tools/diff/lcs/common.go | 179 +++++++++ internal/x/tools/diff/lcs/common_test.go | 140 +++++++ internal/x/tools/diff/lcs/doc.go | 156 ++++++++ internal/x/tools/diff/lcs/git.sh | 33 ++ internal/x/tools/diff/lcs/labels.go | 55 +++ internal/x/tools/diff/lcs/old.go | 480 +++++++++++++++++++++++ internal/x/tools/diff/lcs/old_test.go | 251 ++++++++++++ internal/x/tools/diff/lcs/sequence.go | 113 ++++++ internal/x/tools/diff/myers/diff.go | 246 ++++++++++++ internal/x/tools/diff/ndiff.go | 99 +++++ internal/x/tools/diff/readme.md | 8 + internal/x/tools/diff/unified.go | 251 ++++++++++++ 13 files changed, 2187 insertions(+) create mode 100644 internal/x/tools/diff/diff.go create mode 100644 internal/x/tools/diff/lcs/common.go create mode 100644 internal/x/tools/diff/lcs/common_test.go create mode 100644 internal/x/tools/diff/lcs/doc.go create mode 100644 internal/x/tools/diff/lcs/git.sh create mode 100644 internal/x/tools/diff/lcs/labels.go create mode 100644 internal/x/tools/diff/lcs/old.go create mode 100644 internal/x/tools/diff/lcs/old_test.go create mode 100644 internal/x/tools/diff/lcs/sequence.go create mode 100644 internal/x/tools/diff/myers/diff.go create mode 100644 internal/x/tools/diff/ndiff.go create mode 100644 internal/x/tools/diff/readme.md create mode 100644 internal/x/tools/diff/unified.go diff --git a/internal/x/tools/diff/diff.go b/internal/x/tools/diff/diff.go new file mode 100644 index 000000000000..a13547b7a7e3 --- /dev/null +++ b/internal/x/tools/diff/diff.go @@ -0,0 +1,176 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package diff computes differences between text files or strings. +package diff + +import ( + "fmt" + "sort" + "strings" +) + +// An Edit describes the replacement of a portion of a text file. +type Edit struct { + Start, End int // byte offsets of the region to replace + New string // the replacement +} + +func (e Edit) String() string { + return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New) +} + +// Apply applies a sequence of edits to the src buffer and returns the +// result. Edits are applied in order of start offset; edits with the +// same start offset are applied in they order they were provided. +// +// Apply returns an error if any edit is out of bounds, +// or if any pair of edits is overlapping. +func Apply(src string, edits []Edit) (string, error) { + edits, size, err := validate(src, edits) + if err != nil { + return "", err + } + + // Apply edits. + out := make([]byte, 0, size) + lastEnd := 0 + for _, edit := range edits { + if lastEnd < edit.Start { + out = append(out, src[lastEnd:edit.Start]...) + } + out = append(out, edit.New...) + lastEnd = edit.End + } + out = append(out, src[lastEnd:]...) + + if len(out) != size { + panic("wrong size") + } + + return string(out), nil +} + +// ApplyBytes is like Apply, but it accepts a byte slice. +// The result is always a new array. +func ApplyBytes(src []byte, edits []Edit) ([]byte, error) { + res, err := Apply(string(src), edits) + return []byte(res), err +} + +// validate checks that edits are consistent with src, +// and returns the size of the patched output. +// It may return a different slice. +func validate(src string, edits []Edit) ([]Edit, int, error) { + if !sort.IsSorted(editsSort(edits)) { + edits = append([]Edit(nil), edits...) + SortEdits(edits) + } + + // Check validity of edits and compute final size. + size := len(src) + lastEnd := 0 + for _, edit := range edits { + if !(0 <= edit.Start && edit.Start <= edit.End && edit.End <= len(src)) { + return nil, 0, fmt.Errorf("diff has out-of-bounds edits") + } + if edit.Start < lastEnd { + return nil, 0, fmt.Errorf("diff has overlapping edits") + } + size += len(edit.New) + edit.Start - edit.End + lastEnd = edit.End + } + + return edits, size, nil +} + +// SortEdits orders a slice of Edits by (start, end) offset. +// This ordering puts insertions (end = start) before deletions +// (end > start) at the same point, but uses a stable sort to preserve +// the order of multiple insertions at the same point. +// (Apply detects multiple deletions at the same point as an error.) +func SortEdits(edits []Edit) { + sort.Stable(editsSort(edits)) +} + +type editsSort []Edit + +func (a editsSort) Len() int { return len(a) } +func (a editsSort) Less(i, j int) bool { + if cmp := a[i].Start - a[j].Start; cmp != 0 { + return cmp < 0 + } + return a[i].End < a[j].End +} +func (a editsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// lineEdits expands and merges a sequence of edits so that each +// resulting edit replaces one or more complete lines. +// See ApplyEdits for preconditions. +func lineEdits(src string, edits []Edit) ([]Edit, error) { + edits, _, err := validate(src, edits) + if err != nil { + return nil, err + } + + // Do all deletions begin and end at the start of a line, + // and all insertions end with a newline? + // (This is merely a fast path.) + for _, edit := range edits { + if edit.Start >= len(src) || // insertion at EOF + edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start + edit.End > 0 && src[edit.End-1] != '\n' || // not at line start + edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert + goto expand // slow path + } + } + return edits, nil // aligned + +expand: + if len(edits) == 0 { + return edits, nil // no edits (unreachable due to fast path) + } + expanded := make([]Edit, 0, len(edits)) // a guess + prev := edits[0] + // TODO(adonovan): opt: start from the first misaligned edit. + // TODO(adonovan): opt: avoid quadratic cost of string += string. + for _, edit := range edits[1:] { + between := src[prev.End:edit.Start] + if !strings.Contains(between, "\n") { + // overlapping lines: combine with previous edit. + prev.New += between + edit.New + prev.End = edit.End + } else { + // non-overlapping lines: flush previous edit. + expanded = append(expanded, expandEdit(prev, src)) + prev = edit + } + } + return append(expanded, expandEdit(prev, src)), nil // flush final edit +} + +// expandEdit returns edit expanded to complete whole lines. +func expandEdit(edit Edit, src string) Edit { + // Expand start left to start of line. + // (delta is the zero-based column number of start.) + start := edit.Start + if delta := start - 1 - strings.LastIndex(src[:start], "\n"); delta > 0 { + edit.Start -= delta + edit.New = src[start-delta:start] + edit.New + } + + // Expand end right to end of line. + end := edit.End + if end > 0 && src[end-1] != '\n' || + edit.New != "" && edit.New[len(edit.New)-1] != '\n' { + if nl := strings.IndexByte(src[end:], '\n'); nl < 0 { + edit.End = len(src) // extend to EOF + } else { + edit.End = end + nl + 1 // extend beyond \n + } + } + edit.New += src[end:edit.End] + + return edit +} diff --git a/internal/x/tools/diff/lcs/common.go b/internal/x/tools/diff/lcs/common.go new file mode 100644 index 000000000000..c3e82dd26839 --- /dev/null +++ b/internal/x/tools/diff/lcs/common.go @@ -0,0 +1,179 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lcs + +import ( + "log" + "sort" +) + +// lcs is a longest common sequence +type lcs []diag + +// A diag is a piece of the edit graph where A[X+i] == B[Y+i], for 0<=i l[j].Len + }) + return l +} + +// validate that the elements of the lcs do not overlap +// (can only happen when the two-sided algorithm ends early) +// expects the lcs to be sorted +func (l lcs) valid() bool { + for i := 1; i < len(l); i++ { + if l[i-1].X+l[i-1].Len > l[i].X { + return false + } + if l[i-1].Y+l[i-1].Len > l[i].Y { + return false + } + } + return true +} + +// repair overlapping lcs +// only called if two-sided stops early +func (l lcs) fix() lcs { + // from the set of diagonals in l, find a maximal non-conflicting set + // this problem may be NP-complete, but we use a greedy heuristic, + // which is quadratic, but with a better data structure, could be D log D. + // indepedent is not enough: {0,3,1} and {3,0,2} can't both occur in an lcs + // which has to have monotone x and y + if len(l) == 0 { + return nil + } + sort.Slice(l, func(i, j int) bool { return l[i].Len > l[j].Len }) + tmp := make(lcs, 0, len(l)) + tmp = append(tmp, l[0]) + for i := 1; i < len(l); i++ { + var dir direction + nxt := l[i] + for _, in := range tmp { + if dir, nxt = overlap(in, nxt); dir == empty || dir == bad { + break + } + } + if nxt.Len > 0 && dir != bad { + tmp = append(tmp, nxt) + } + } + tmp.sort() + if false && !tmp.valid() { // debug checking + log.Fatalf("here %d", len(tmp)) + } + return tmp +} + +type direction int + +const ( + empty direction = iota // diag is empty (so not in lcs) + leftdown // proposed acceptably to the left and below + rightup // proposed diag is acceptably to the right and above + bad // proposed diag is inconsistent with the lcs so far +) + +// overlap trims the proposed diag prop so it doesn't overlap with +// the existing diag that has already been added to the lcs. +func overlap(exist, prop diag) (direction, diag) { + if prop.X <= exist.X && exist.X < prop.X+prop.Len { + // remove the end of prop where it overlaps with the X end of exist + delta := prop.X + prop.Len - exist.X + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + } + if exist.X <= prop.X && prop.X < exist.X+exist.Len { + // remove the beginning of prop where overlaps with exist + delta := exist.X + exist.Len - prop.X + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + prop.X += delta + prop.Y += delta + } + if prop.Y <= exist.Y && exist.Y < prop.Y+prop.Len { + // remove the end of prop that overlaps (in Y) with exist + delta := prop.Y + prop.Len - exist.Y + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + } + if exist.Y <= prop.Y && prop.Y < exist.Y+exist.Len { + // remove the beginning of peop that overlaps with exist + delta := exist.Y + exist.Len - prop.Y + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + prop.X += delta // no test reaches this code + prop.Y += delta + } + if prop.X+prop.Len <= exist.X && prop.Y+prop.Len <= exist.Y { + return leftdown, prop + } + if exist.X+exist.Len <= prop.X && exist.Y+exist.Len <= prop.Y { + return rightup, prop + } + // prop can't be in an lcs that contains exist + return bad, prop +} + +// manipulating Diag and lcs + +// prepend a diagonal (x,y)-(x+1,y+1) segment either to an empty lcs +// or to its first Diag. prepend is only called to extend diagonals +// the backward direction. +func (lcs lcs) prepend(x, y int) lcs { + if len(lcs) > 0 { + d := &lcs[0] + if int(d.X) == x+1 && int(d.Y) == y+1 { + // extend the diagonal down and to the left + d.X, d.Y = int(x), int(y) + d.Len++ + return lcs + } + } + + r := diag{X: int(x), Y: int(y), Len: 1} + lcs = append([]diag{r}, lcs...) + return lcs +} + +// append appends a diagonal, or extends the existing one. +// by adding the edge (x,y)-(x+1.y+1). append is only called +// to extend diagonals in the forward direction. +func (lcs lcs) append(x, y int) lcs { + if len(lcs) > 0 { + last := &lcs[len(lcs)-1] + // Expand last element if adjoining. + if last.X+last.Len == x && last.Y+last.Len == y { + last.Len++ + return lcs + } + } + + return append(lcs, diag{X: x, Y: y, Len: 1}) +} + +// enforce constraint on d, k +func ok(d, k int) bool { + return d >= 0 && -d <= k && k <= d +} diff --git a/internal/x/tools/diff/lcs/common_test.go b/internal/x/tools/diff/lcs/common_test.go new file mode 100644 index 000000000000..f19245e404ce --- /dev/null +++ b/internal/x/tools/diff/lcs/common_test.go @@ -0,0 +1,140 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lcs + +import ( + "log" + "math/rand" + "strings" + "testing" +) + +type Btest struct { + a, b string + lcs []string +} + +var Btests = []Btest{ + {"aaabab", "abaab", []string{"abab", "aaab"}}, + {"aabbba", "baaba", []string{"aaba"}}, + {"cabbx", "cbabx", []string{"cabx", "cbbx"}}, + {"c", "cb", []string{"c"}}, + {"aaba", "bbb", []string{"b"}}, + {"bbaabb", "b", []string{"b"}}, + {"baaabb", "bbaba", []string{"bbb", "baa", "bab"}}, + {"baaabb", "abbab", []string{"abb", "bab", "aab"}}, + {"baaba", "aaabba", []string{"aaba"}}, + {"ca", "cba", []string{"ca"}}, + {"ccbcbc", "abba", []string{"bb"}}, + {"ccbcbc", "aabba", []string{"bb"}}, + {"ccb", "cba", []string{"cb"}}, + {"caef", "axe", []string{"ae"}}, + {"bbaabb", "baabb", []string{"baabb"}}, + // Example from Myers: + {"abcabba", "cbabac", []string{"caba", "baba", "cbba"}}, + {"3456aaa", "aaa", []string{"aaa"}}, + {"aaa", "aaa123", []string{"aaa"}}, + {"aabaa", "aacaa", []string{"aaaa"}}, + {"1a", "a", []string{"a"}}, + {"abab", "bb", []string{"bb"}}, + {"123", "ab", []string{""}}, + {"a", "b", []string{""}}, + {"abc", "123", []string{""}}, + {"aa", "aa", []string{"aa"}}, + {"abcde", "12345", []string{""}}, + {"aaa3456", "aaa", []string{"aaa"}}, + {"abcde", "12345a", []string{"a"}}, + {"ab", "123", []string{""}}, + {"1a2", "a", []string{"a"}}, + // for two-sided + {"babaab", "cccaba", []string{"aba"}}, + {"aabbab", "cbcabc", []string{"bab"}}, + {"abaabb", "bcacab", []string{"baab"}}, + {"abaabb", "abaaaa", []string{"abaa"}}, + {"bababb", "baaabb", []string{"baabb"}}, + {"abbbaa", "cabacc", []string{"aba"}}, + {"aabbaa", "aacaba", []string{"aaaa", "aaba"}}, +} + +func init() { + log.SetFlags(log.Lshortfile) +} + +func check(t *testing.T, str string, lcs lcs, want []string) { + t.Helper() + if !lcs.valid() { + t.Errorf("bad lcs %v", lcs) + } + var got strings.Builder + for _, dd := range lcs { + got.WriteString(str[dd.X : dd.X+dd.Len]) + } + ans := got.String() + for _, w := range want { + if ans == w { + return + } + } + t.Fatalf("str=%q lcs=%v want=%q got=%q", str, lcs, want, ans) +} + +func checkDiffs(t *testing.T, before string, diffs []Diff, after string) { + t.Helper() + var ans strings.Builder + sofar := 0 // index of position in before + for _, d := range diffs { + if sofar < d.Start { + ans.WriteString(before[sofar:d.Start]) + } + ans.WriteString(after[d.ReplStart:d.ReplEnd]) + sofar = d.End + } + ans.WriteString(before[sofar:]) + if ans.String() != after { + t.Fatalf("diff %v took %q to %q, not to %q", diffs, before, ans.String(), after) + } +} + +func lcslen(l lcs) int { + ans := 0 + for _, d := range l { + ans += int(d.Len) + } + return ans +} + +// return a random string of length n made of characters from s +func randstr(s string, n int) string { + src := []rune(s) + x := make([]rune, n) + for i := 0; i < n; i++ { + x[i] = src[rand.Intn(len(src))] + } + return string(x) +} + +func TestLcsFix(t *testing.T) { + tests := []struct{ before, after lcs }{ + {lcs{diag{0, 0, 3}, diag{2, 2, 5}, diag{3, 4, 5}, diag{8, 9, 4}}, lcs{diag{0, 0, 2}, diag{2, 2, 1}, diag{3, 4, 5}, diag{8, 9, 4}}}, + {lcs{diag{1, 1, 6}, diag{6, 12, 3}}, lcs{diag{1, 1, 5}, diag{6, 12, 3}}}, + {lcs{diag{0, 0, 4}, diag{3, 5, 4}}, lcs{diag{0, 0, 3}, diag{3, 5, 4}}}, + {lcs{diag{0, 20, 1}, diag{0, 0, 3}, diag{1, 20, 4}}, lcs{diag{0, 0, 3}, diag{3, 22, 2}}}, + {lcs{diag{0, 0, 4}, diag{1, 1, 2}}, lcs{diag{0, 0, 4}}}, + {lcs{diag{0, 0, 4}}, lcs{diag{0, 0, 4}}}, + {lcs{}, lcs{}}, + {lcs{diag{0, 0, 4}, diag{1, 1, 6}, diag{3, 3, 2}}, lcs{diag{0, 0, 1}, diag{1, 1, 6}}}, + } + for n, x := range tests { + got := x.before.fix() + if len(got) != len(x.after) { + t.Errorf("got %v, expected %v, for %v", got, x.after, x.before) + } + olen := lcslen(x.after) + glen := lcslen(got) + if olen != glen { + t.Errorf("%d: lens(%d,%d) differ, %v, %v, %v", n, glen, olen, got, x.after, x.before) + } + } +} diff --git a/internal/x/tools/diff/lcs/doc.go b/internal/x/tools/diff/lcs/doc.go new file mode 100644 index 000000000000..9029dd20b3d5 --- /dev/null +++ b/internal/x/tools/diff/lcs/doc.go @@ -0,0 +1,156 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// package lcs contains code to find longest-common-subsequences +// (and diffs) +package lcs + +/* +Compute longest-common-subsequences of two slices A, B using +algorithms from Myers' paper. A longest-common-subsequence +(LCS from now on) of A and B is a maximal set of lexically increasing +pairs of subscripts (x,y) with A[x]==B[y]. There may be many LCS, but +they all have the same length. An LCS determines a sequence of edits +that changes A into B. + +The key concept is the edit graph of A and B. +If A has length N and B has length M, then the edit graph has +vertices v[i][j] for 0 <= i <= N, 0 <= j <= M. There is a +horizontal edge from v[i][j] to v[i+1][j] whenever both are in +the graph, and a vertical edge from v[i][j] to f[i][j+1] similarly. +When A[i] == B[j] there is a diagonal edge from v[i][j] to v[i+1][j+1]. + +A path between in the graph between (0,0) and (N,M) determines a sequence +of edits converting A into B: each horizontal edge corresponds to removing +an element of A, and each vertical edge corresponds to inserting an +element of B. + +A vertex (x,y) is on (forward) diagonal k if x-y=k. A path in the graph +is of length D if it has D non-diagonal edges. The algorithms generate +forward paths (in which at least one of x,y increases at each edge), +or backward paths (in which at least one of x,y decreases at each edge), +or a combination. (Note that the orientation is the traditional mathematical one, +with the origin in the lower-left corner.) + +Here is the edit graph for A:"aabbaa", B:"aacaba". (I know the diagonals look weird.) + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + b | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + c | | | | | | | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a a b b a a + + +The algorithm labels a vertex (x,y) with D,k if it is on diagonal k and at +the end of a maximal path of length D. (Because x-y=k it suffices to remember +only the x coordinate of the vertex.) + +The forward algorithm: Find the longest diagonal starting at (0,0) and +label its end with D=0,k=0. From that vertex take a vertical step and +then follow the longest diagonal (up and to the right), and label that vertex +with D=1,k=-1. From the D=0,k=0 point take a horizontal step and the follow +the longest diagonal (up and to the right) and label that vertex +D=1,k=1. In the same way, having labelled all the D vertices, +from a vertex labelled D,k find two vertices +tentatively labelled D+1,k-1 and D+1,k+1. There may be two on the same +diagonal, in which case take the one with the larger x. + +Eventually the path gets to (N,M), and the diagonals on it are the LCS. + +Here is the edit graph with the ends of D-paths labelled. (So, for instance, +0/2,2 indicates that x=2,y=2 is labelled with 0, as it should be, since the first +step is to go up the longest diagonal from (0,0).) +A:"aabbaa", B:"aacaba" + โŠ™ ------- โŠ™ ------- โŠ™ -------(3/3,6)------- โŠ™ -------(3/5,6)-------(4/6,6) + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ -------(2/3,5)------- โŠ™ ------- โŠ™ ------- โŠ™ + b | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ -------(3/5,4)------- โŠ™ + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ -------(1/2,3)-------(2/3,3)------- โŠ™ ------- โŠ™ ------- โŠ™ + c | | | | | | | + โŠ™ ------- โŠ™ -------(0/2,2)-------(1/3,2)-------(2/4,2)-------(3/5,2)-------(4/6,2) + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | | | ___/โ€พโ€พโ€พ | ___/โ€พโ€พโ€พ | + โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ ------- โŠ™ + a a b b a a + +The 4-path is reconstructed starting at (4/6,6), horizontal to (3/5,6), diagonal to (3,4), vertical +to (2/3,3), horizontal to (1/2,3), vertical to (0/2,2), and diagonal to (0,0). As expected, +there are 4 non-diagonal steps, and the diagonals form an LCS. + +There is a symmetric backward algorithm, which gives (backwards labels are prefixed with a colon): +A:"aabbaa", B:"aacaba" + โŠ™ -------- โŠ™ -------- โŠ™ -------- โŠ™ -------- โŠ™ -------- โŠ™ -------- โŠ™ + a | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | + โŠ™ -------- โŠ™ -------- โŠ™ -------- โŠ™ -------- โŠ™ --------(:0/5,5)-------- โŠ™ + b | | | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | | | + โŠ™ -------- โŠ™ -------- โŠ™ --------(:1/3,4)-------- โŠ™ -------- โŠ™ -------- โŠ™ + a | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | + (:3/0,3)--------(:2/1,3)-------- โŠ™ --------(:2/3,3)--------(:1/4,3)-------- โŠ™ -------- โŠ™ + c | | | | | | | + โŠ™ -------- โŠ™ -------- โŠ™ --------(:3/3,2)--------(:2/4,2)-------- โŠ™ -------- โŠ™ + a | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | + (:3/0,1)-------- โŠ™ -------- โŠ™ -------- โŠ™ --------(:3/4,1)-------- โŠ™ -------- โŠ™ + a | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พ | ____/โ€พโ€พโ€พ | + (:4/0,0)-------- โŠ™ -------- โŠ™ -------- โŠ™ --------(:4/4,0)-------- โŠ™ -------- โŠ™ + a a b b a a + +Neither of these is ideal for use in an editor, where it is undesirable to send very long diffs to the +front end. It's tricky to decide exactly what 'very long diffs' means, as "replace A by B" is very short. +We want to control how big D can be, by stopping when it gets too large. The forward algorithm then +privileges common prefixes, and the backward algorithm privileges common suffixes. Either is an undesirable +asymmetry. + +Fortunately there is a two-sided algorithm, implied by results in Myers' paper. Here's what the labels in +the edit graph look like. +A:"aabbaa", B:"aacaba" + โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ + a | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | + โŠ™ --------- โŠ™ --------- โŠ™ --------- (2/3,5) --------- โŠ™ --------- (:0/5,5)--------- โŠ™ + b | | | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | | | + โŠ™ --------- โŠ™ --------- โŠ™ --------- (:1/3,4)--------- โŠ™ --------- โŠ™ --------- โŠ™ + a | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | + โŠ™ --------- (:2/1,3)--------- (1/2,3) ---------(2:2/3,3)--------- (:1/4,3)--------- โŠ™ --------- โŠ™ + c | | | | | | | + โŠ™ --------- โŠ™ --------- (0/2,2) --------- (1/3,2) ---------(2:2/4,2)--------- โŠ™ --------- โŠ™ + a | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | + โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ + a | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | | | ____/โ€พโ€พโ€พโ€พ | ____/โ€พโ€พโ€พโ€พ | + โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ --------- โŠ™ + a a b b a a + +The algorithm stopped when it saw the backwards 2-path ending at (1,3) and the forwards 2-path ending at (3,5). The criterion +is a backwards path ending at (u,v) and a forward path ending at (x,y), where u <= x and the two points are on the same +diagonal. (Here the edgegraph has a diagonal, but the criterion is x-y=u-v.) Myers proves there is a forward +2-path from (0,0) to (1,3), and that together with the backwards 2-path ending at (1,3) gives the expected 4-path. +Unfortunately the forward path has to be constructed by another run of the forward algorithm; it can't be found from the +computed labels. That is the worst case. Had the code noticed (x,y)=(u,v)=(3,3) the whole path could be reconstructed +from the edgegraph. The implementation looks for a number of special cases to try to avoid computing an extra forward path. + +If the two-sided algorithm has stop early (because D has become too large) it will have found a forward LCS and a +backwards LCS. Ideally these go with disjoint prefixes and suffixes of A and B, but disjointness may fail and the two +computed LCS may conflict. (An easy example is where A is a suffix of B, and shares a short prefix. The backwards LCS +is all of A, and the forward LCS is a prefix of A.) The algorithm combines the two +to form a best-effort LCS. In the worst case the forward partial LCS may have to +be recomputed. +*/ + +/* Eugene Myers paper is titled +"An O(ND) Difference Algorithm and Its Variations" +and can be found at +http://www.xmailserver.org/diff2.pdf + +(There is a generic implementation of the algorithm the repository with git hash +b9ad7e4ade3a686d608e44475390ad428e60e7fc) +*/ diff --git a/internal/x/tools/diff/lcs/git.sh b/internal/x/tools/diff/lcs/git.sh new file mode 100644 index 000000000000..b25ba4aac74b --- /dev/null +++ b/internal/x/tools/diff/lcs/git.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright 2022 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# +# Creates a zip file containing all numbered versions +# of the commit history of a large source file, for use +# as input data for the tests of the diff algorithm. +# +# Run script from root of the x/tools repo. + +set -eu + +# WARNING: This script will install the latest version of $file +# The largest real source file in the x/tools repo. +# file=internal/golang/completion/completion.go +# file=internal/golang/diagnostics.go +file=internal/protocol/tsprotocol.go + +tmp=$(mktemp -d) +git log $file | + awk '/^commit / {print $2}' | + nl -ba -nrz | + while read n hash; do + git checkout --quiet $hash $file + cp -f $file $tmp/$n + done +(cd $tmp && zip -q - *) > testdata.zip +rm -fr $tmp +git restore --staged $file +git restore $file +echo "Created testdata.zip" diff --git a/internal/x/tools/diff/lcs/labels.go b/internal/x/tools/diff/lcs/labels.go new file mode 100644 index 000000000000..504913d1da3c --- /dev/null +++ b/internal/x/tools/diff/lcs/labels.go @@ -0,0 +1,55 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lcs + +import ( + "fmt" +) + +// For each D, vec[D] has length D+1, +// and the label for (D, k) is stored in vec[D][(D+k)/2]. +type label struct { + vec [][]int +} + +// Temporary checking DO NOT COMMIT true TO PRODUCTION CODE +const debug = false + +// debugging. check that the (d,k) pair is valid +// (that is, -d<=k<=d and d+k even) +func checkDK(D, k int) { + if k >= -D && k <= D && (D+k)%2 == 0 { + return + } + panic(fmt.Sprintf("out of range, d=%d,k=%d", D, k)) +} + +func (t *label) set(D, k, x int) { + if debug { + checkDK(D, k) + } + for len(t.vec) <= D { + t.vec = append(t.vec, nil) + } + if t.vec[D] == nil { + t.vec[D] = make([]int, D+1) + } + t.vec[D][(D+k)/2] = x // known that D+k is even +} + +func (t *label) get(d, k int) int { + if debug { + checkDK(d, k) + } + return int(t.vec[d][(d+k)/2]) +} + +func newtriang(limit int) label { + if limit < 100 { + // Preallocate if limit is not large. + return label{vec: make([][]int, limit)} + } + return label{} +} diff --git a/internal/x/tools/diff/lcs/old.go b/internal/x/tools/diff/lcs/old.go new file mode 100644 index 000000000000..4353da15ba9a --- /dev/null +++ b/internal/x/tools/diff/lcs/old.go @@ -0,0 +1,480 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lcs + +// TODO(adonovan): remove unclear references to "old" in this package. + +import ( + "fmt" +) + +// A Diff is a replacement of a portion of A by a portion of B. +type Diff struct { + Start, End int // offsets of portion to delete in A + ReplStart, ReplEnd int // offset of replacement text in B +} + +// DiffStrings returns the differences between two strings. +// It does not respect rune boundaries. +func DiffStrings(a, b string) []Diff { return diff(stringSeqs{a, b}) } + +// DiffBytes returns the differences between two byte sequences. +// It does not respect rune boundaries. +func DiffBytes(a, b []byte) []Diff { return diff(bytesSeqs{a, b}) } + +// DiffRunes returns the differences between two rune sequences. +func DiffRunes(a, b []rune) []Diff { return diff(runesSeqs{a, b}) } + +func diff(seqs sequences) []Diff { + // A limit on how deeply the LCS algorithm should search. The value is just a guess. + const maxDiffs = 100 + diff, _ := compute(seqs, twosided, maxDiffs/2) + return diff +} + +// compute computes the list of differences between two sequences, +// along with the LCS. It is exercised directly by tests. +// The algorithm is one of {forward, backward, twosided}. +func compute(seqs sequences, algo func(*editGraph) lcs, limit int) ([]Diff, lcs) { + if limit <= 0 { + limit = 1 << 25 // effectively infinity + } + alen, blen := seqs.lengths() + g := &editGraph{ + seqs: seqs, + vf: newtriang(limit), + vb: newtriang(limit), + limit: limit, + ux: alen, + uy: blen, + delta: alen - blen, + } + lcs := algo(g) + diffs := lcs.toDiffs(alen, blen) + return diffs, lcs +} + +// editGraph carries the information for computing the lcs of two sequences. +type editGraph struct { + seqs sequences + vf, vb label // forward and backward labels + + limit int // maximal value of D + // the bounding rectangle of the current edit graph + lx, ly, ux, uy int + delta int // common subexpression: (ux-lx)-(uy-ly) +} + +// toDiffs converts an LCS to a list of edits. +func (lcs lcs) toDiffs(alen, blen int) []Diff { + var diffs []Diff + var pa, pb int // offsets in a, b + for _, l := range lcs { + if pa < l.X || pb < l.Y { + diffs = append(diffs, Diff{pa, l.X, pb, l.Y}) + } + pa = l.X + l.Len + pb = l.Y + l.Len + } + if pa < alen || pb < blen { + diffs = append(diffs, Diff{pa, alen, pb, blen}) + } + return diffs +} + +// --- FORWARD --- + +// fdone decides if the forward path has reached the upper right +// corner of the rectangle. If so, it also returns the computed lcs. +func (e *editGraph) fdone(D, k int) (bool, lcs) { + // x, y, k are relative to the rectangle + x := e.vf.get(D, k) + y := x - k + if x == e.ux && y == e.uy { + return true, e.forwardlcs(D, k) + } + return false, nil +} + +// run the forward algorithm, until success or up to the limit on D. +func forward(e *editGraph) lcs { + e.setForward(0, 0, e.lx) + if ok, ans := e.fdone(0, 0); ok { + return ans + } + // from D to D+1 + for D := 0; D < e.limit; D++ { + e.setForward(D+1, -(D + 1), e.getForward(D, -D)) + if ok, ans := e.fdone(D+1, -(D + 1)); ok { + return ans + } + e.setForward(D+1, D+1, e.getForward(D, D)+1) + if ok, ans := e.fdone(D+1, D+1); ok { + return ans + } + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get backwards + lookv := e.lookForward(k, e.getForward(D, k-1)+1) + lookh := e.lookForward(k, e.getForward(D, k+1)) + if lookv > lookh { + e.setForward(D+1, k, lookv) + } else { + e.setForward(D+1, k, lookh) + } + if ok, ans := e.fdone(D+1, k); ok { + return ans + } + } + } + // D is too large + // find the D path with maximal x+y inside the rectangle and + // use that to compute the found part of the lcs + kmax := -e.limit - 1 + diagmax := -1 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getForward(e.limit, k) + y := x - k + if x+y > diagmax && x <= e.ux && y <= e.uy { + diagmax, kmax = x+y, k + } + } + return e.forwardlcs(e.limit, kmax) +} + +// recover the lcs by backtracking from the farthest point reached +func (e *editGraph) forwardlcs(D, k int) lcs { + var ans lcs + for x := e.getForward(D, k); x != 0 || x-k != 0; { + if ok(D-1, k-1) && x-1 == e.getForward(D-1, k-1) { + // if (x-1,y) is labelled D-1, x--,D--,k--,continue + D, k, x = D-1, k-1, x-1 + continue + } else if ok(D-1, k+1) && x == e.getForward(D-1, k+1) { + // if (x,y-1) is labelled D-1, x, D--,k++, continue + D, k = D-1, k+1 + continue + } + // if (x-1,y-1)--(x,y) is a diagonal, prepend,x--,y--, continue + y := x - k + ans = ans.prepend(x+e.lx-1, y+e.ly-1) + x-- + } + return ans +} + +// start at (x,y), go up the diagonal as far as possible, +// and label the result with d +func (e *editGraph) lookForward(k, relx int) int { + rely := relx - k + x, y := relx+e.lx, rely+e.ly + if x < e.ux && y < e.uy { + x += e.seqs.commonPrefixLen(x, e.ux, y, e.uy) + } + return x +} + +func (e *editGraph) setForward(d, k, relx int) { + x := e.lookForward(k, relx) + e.vf.set(d, k, x-e.lx) +} + +func (e *editGraph) getForward(d, k int) int { + x := e.vf.get(d, k) + return x +} + +// --- BACKWARD --- + +// bdone decides if the backward path has reached the lower left corner +func (e *editGraph) bdone(D, k int) (bool, lcs) { + // x, y, k are relative to the rectangle + x := e.vb.get(D, k) + y := x - (k + e.delta) + if x == 0 && y == 0 { + return true, e.backwardlcs(D, k) + } + return false, nil +} + +// run the backward algorithm, until success or up to the limit on D. +func backward(e *editGraph) lcs { + e.setBackward(0, 0, e.ux) + if ok, ans := e.bdone(0, 0); ok { + return ans + } + // from D to D+1 + for D := 0; D < e.limit; D++ { + e.setBackward(D+1, -(D + 1), e.getBackward(D, -D)-1) + if ok, ans := e.bdone(D+1, -(D + 1)); ok { + return ans + } + e.setBackward(D+1, D+1, e.getBackward(D, D)) + if ok, ans := e.bdone(D+1, D+1); ok { + return ans + } + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get wrong + lookv := e.lookBackward(k, e.getBackward(D, k-1)) + lookh := e.lookBackward(k, e.getBackward(D, k+1)-1) + if lookv < lookh { + e.setBackward(D+1, k, lookv) + } else { + e.setBackward(D+1, k, lookh) + } + if ok, ans := e.bdone(D+1, k); ok { + return ans + } + } + } + + // D is too large + // find the D path with minimal x+y inside the rectangle and + // use that to compute the part of the lcs found + kmax := -e.limit - 1 + diagmin := 1 << 25 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getBackward(e.limit, k) + y := x - (k + e.delta) + if x+y < diagmin && x >= 0 && y >= 0 { + diagmin, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no paths when limit=%d?", e.limit)) + } + return e.backwardlcs(e.limit, kmax) +} + +// recover the lcs by backtracking +func (e *editGraph) backwardlcs(D, k int) lcs { + var ans lcs + for x := e.getBackward(D, k); x != e.ux || x-(k+e.delta) != e.uy; { + if ok(D-1, k-1) && x == e.getBackward(D-1, k-1) { + // D--, k--, x unchanged + D, k = D-1, k-1 + continue + } else if ok(D-1, k+1) && x+1 == e.getBackward(D-1, k+1) { + // D--, k++, x++ + D, k, x = D-1, k+1, x+1 + continue + } + y := x - (k + e.delta) + ans = ans.append(x+e.lx, y+e.ly) + x++ + } + return ans +} + +// start at (x,y), go down the diagonal as far as possible, +func (e *editGraph) lookBackward(k, relx int) int { + rely := relx - (k + e.delta) // forward k = k + e.delta + x, y := relx+e.lx, rely+e.ly + if x > 0 && y > 0 { + x -= e.seqs.commonSuffixLen(0, x, 0, y) + } + return x +} + +// convert to rectangle, and label the result with d +func (e *editGraph) setBackward(d, k, relx int) { + x := e.lookBackward(k, relx) + e.vb.set(d, k, x-e.lx) +} + +func (e *editGraph) getBackward(d, k int) int { + x := e.vb.get(d, k) + return x +} + +// -- TWOSIDED --- + +func twosided(e *editGraph) lcs { + // The termination condition could be improved, as either the forward + // or backward pass could succeed before Myers' Lemma applies. + // Aside from questions of efficiency (is the extra testing cost-effective) + // this is more likely to matter when e.limit is reached. + e.setForward(0, 0, e.lx) + e.setBackward(0, 0, e.ux) + + // from D to D+1 + for D := 0; D < e.limit; D++ { + // just finished a backwards pass, so check + if got, ok := e.twoDone(D, D); ok { + return e.twolcs(D, D, got) + } + // do a forwards pass (D to D+1) + e.setForward(D+1, -(D + 1), e.getForward(D, -D)) + e.setForward(D+1, D+1, e.getForward(D, D)+1) + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get backwards + lookv := e.lookForward(k, e.getForward(D, k-1)+1) + lookh := e.lookForward(k, e.getForward(D, k+1)) + if lookv > lookh { + e.setForward(D+1, k, lookv) + } else { + e.setForward(D+1, k, lookh) + } + } + // just did a forward pass, so check + if got, ok := e.twoDone(D+1, D); ok { + return e.twolcs(D+1, D, got) + } + // do a backward pass, D to D+1 + e.setBackward(D+1, -(D + 1), e.getBackward(D, -D)-1) + e.setBackward(D+1, D+1, e.getBackward(D, D)) + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get wrong + lookv := e.lookBackward(k, e.getBackward(D, k-1)) + lookh := e.lookBackward(k, e.getBackward(D, k+1)-1) + if lookv < lookh { + e.setBackward(D+1, k, lookv) + } else { + e.setBackward(D+1, k, lookh) + } + } + } + + // D too large. combine a forward and backward partial lcs + // first, a forward one + kmax := -e.limit - 1 + diagmax := -1 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getForward(e.limit, k) + y := x - k + if x+y > diagmax && x <= e.ux && y <= e.uy { + diagmax, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no forward paths when limit=%d?", e.limit)) + } + lcs := e.forwardlcs(e.limit, kmax) + // now a backward one + // find the D path with minimal x+y inside the rectangle and + // use that to compute the lcs + diagmin := 1 << 25 // infinity + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getBackward(e.limit, k) + y := x - (k + e.delta) + if x+y < diagmin && x >= 0 && y >= 0 { + diagmin, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no backward paths when limit=%d?", e.limit)) + } + lcs = append(lcs, e.backwardlcs(e.limit, kmax)...) + // These may overlap (e.forwardlcs and e.backwardlcs return sorted lcs) + ans := lcs.fix() + return ans +} + +// Does Myers' Lemma apply? +func (e *editGraph) twoDone(df, db int) (int, bool) { + if (df+db+e.delta)%2 != 0 { + return 0, false // diagonals cannot overlap + } + kmin := -db + e.delta + if -df > kmin { + kmin = -df + } + kmax := db + e.delta + if df < kmax { + kmax = df + } + for k := kmin; k <= kmax; k += 2 { + x := e.vf.get(df, k) + u := e.vb.get(db, k-e.delta) + if u <= x { + // is it worth looking at all the other k? + for l := k; l <= kmax; l += 2 { + x := e.vf.get(df, l) + y := x - l + u := e.vb.get(db, l-e.delta) + v := u - l + if x == u || u == 0 || v == 0 || y == e.uy || x == e.ux { + return l, true + } + } + return k, true + } + } + return 0, false +} + +func (e *editGraph) twolcs(df, db, kf int) lcs { + // db==df || db+1==df + x := e.vf.get(df, kf) + y := x - kf + kb := kf - e.delta + u := e.vb.get(db, kb) + v := u - kf + + // Myers proved there is a df-path from (0,0) to (u,v) + // and a db-path from (x,y) to (N,M). + // In the first case the overall path is the forward path + // to (u,v) followed by the backward path to (N,M). + // In the second case the path is the backward path to (x,y) + // followed by the forward path to (x,y) from (0,0). + + // Look for some special cases to avoid computing either of these paths. + if x == u { + // "babaab" "cccaba" + // already patched together + lcs := e.forwardlcs(df, kf) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + + // is (u-1,v) or (u,v-1) labelled df-1? + // if so, that forward df-1-path plus a horizontal or vertical edge + // is the df-path to (u,v), then plus the db-path to (N,M) + if u > 0 && ok(df-1, u-1-v) && e.vf.get(df-1, u-1-v) == u-1 { + // "aabbab" "cbcabc" + lcs := e.forwardlcs(df-1, u-1-v) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + if v > 0 && ok(df-1, (u-(v-1))) && e.vf.get(df-1, u-(v-1)) == u { + // "abaabb" "bcacab" + lcs := e.forwardlcs(df-1, u-(v-1)) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + + // The path can't possibly contribute to the lcs because it + // is all horizontal or vertical edges + if u == 0 || v == 0 || x == e.ux || y == e.uy { + // "abaabb" "abaaaa" + if u == 0 || v == 0 { + return e.backwardlcs(db, kb) + } + return e.forwardlcs(df, kf) + } + + // is (x+1,y) or (x,y+1) labelled db-1? + if x+1 <= e.ux && ok(db-1, x+1-y-e.delta) && e.vb.get(db-1, x+1-y-e.delta) == x+1 { + // "bababb" "baaabb" + lcs := e.backwardlcs(db-1, kb+1) + lcs = append(lcs, e.forwardlcs(df, kf)...) + return lcs.sort() + } + if y+1 <= e.uy && ok(db-1, x-(y+1)-e.delta) && e.vb.get(db-1, x-(y+1)-e.delta) == x { + // "abbbaa" "cabacc" + lcs := e.backwardlcs(db-1, kb-1) + lcs = append(lcs, e.forwardlcs(df, kf)...) + return lcs.sort() + } + + // need to compute another path + // "aabbaa" "aacaba" + lcs := e.backwardlcs(db, kb) + oldx, oldy := e.ux, e.uy + e.ux = u + e.uy = v + lcs = append(lcs, forward(e)...) + e.ux, e.uy = oldx, oldy + return lcs.sort() +} diff --git a/internal/x/tools/diff/lcs/old_test.go b/internal/x/tools/diff/lcs/old_test.go new file mode 100644 index 000000000000..ddc3bde0ed27 --- /dev/null +++ b/internal/x/tools/diff/lcs/old_test.go @@ -0,0 +1,251 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lcs + +import ( + "fmt" + "log" + "math/rand" + "os" + "strings" + "testing" +) + +func TestAlgosOld(t *testing.T) { + for i, algo := range []func(*editGraph) lcs{forward, backward, twosided} { + t.Run(strings.Fields("forward backward twosided")[i], func(t *testing.T) { + for _, tx := range Btests { + lim := len(tx.a) + len(tx.b) + + diffs, lcs := compute(stringSeqs{tx.a, tx.b}, algo, lim) + check(t, tx.a, lcs, tx.lcs) + checkDiffs(t, tx.a, diffs, tx.b) + + diffs, lcs = compute(stringSeqs{tx.b, tx.a}, algo, lim) + check(t, tx.b, lcs, tx.lcs) + checkDiffs(t, tx.b, diffs, tx.a) + } + }) + } +} + +func TestIntOld(t *testing.T) { + // need to avoid any characters in btests + lfill, rfill := "AAAAAAAAAAAA", "BBBBBBBBBBBB" + for _, tx := range Btests { + if len(tx.a) < 2 || len(tx.b) < 2 { + continue + } + left := tx.a + lfill + right := tx.b + rfill + lim := len(tx.a) + len(tx.b) + diffs, lcs := compute(stringSeqs{left, right}, twosided, lim) + check(t, left, lcs, tx.lcs) + checkDiffs(t, left, diffs, right) + diffs, lcs = compute(stringSeqs{right, left}, twosided, lim) + check(t, right, lcs, tx.lcs) + checkDiffs(t, right, diffs, left) + + left = lfill + tx.a + right = rfill + tx.b + diffs, lcs = compute(stringSeqs{left, right}, twosided, lim) + check(t, left, lcs, tx.lcs) + checkDiffs(t, left, diffs, right) + diffs, lcs = compute(stringSeqs{right, left}, twosided, lim) + check(t, right, lcs, tx.lcs) + checkDiffs(t, right, diffs, left) + } +} + +func TestSpecialOld(t *testing.T) { // exercises lcs.fix + a := "golang.org/x/tools/intern" + b := "github.com/google/safehtml/template\"\n\t\"golang.org/x/tools/intern" + diffs, lcs := compute(stringSeqs{a, b}, twosided, 4) + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } +} + +func TestRegressionOld001(t *testing.T) { + a := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" + + b := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/safehtml/template\"\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" + for i := 1; i < len(b); i++ { + diffs, lcs := compute(stringSeqs{a, b}, twosided, i) // 14 from gopls + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } + checkDiffs(t, a, diffs, b) + } +} + +func TestRegressionOld002(t *testing.T) { + a := "n\"\n)\n" + b := "n\"\n\t\"golang.org/x//nnal/stack\"\n)\n" + for i := 1; i <= len(b); i++ { + diffs, lcs := compute(stringSeqs{a, b}, twosided, i) + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } + checkDiffs(t, a, diffs, b) + } +} + +func TestRegressionOld003(t *testing.T) { + a := "golang.org/x/hello v1.0.0\nrequire golang.org/x/unused v1" + b := "golang.org/x/hello v1" + for i := 1; i <= len(a); i++ { + diffs, lcs := compute(stringSeqs{a, b}, twosided, i) + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } + checkDiffs(t, a, diffs, b) + } +} + +func TestRandOld(t *testing.T) { + rand.Seed(1) + for i := 0; i < 1000; i++ { + // TODO(adonovan): use ASCII and bytesSeqs here? The use of + // non-ASCII isn't relevant to the property exercised by the test. + a := []rune(randstr("abฯ‰", 16)) + b := []rune(randstr("abฯ‰c", 16)) + seq := runesSeqs{a, b} + + const lim = 24 // large enough to get true lcs + _, forw := compute(seq, forward, lim) + _, back := compute(seq, backward, lim) + _, two := compute(seq, twosided, lim) + if lcslen(two) != lcslen(forw) || lcslen(forw) != lcslen(back) { + t.Logf("\n%v\n%v\n%v", forw, back, two) + t.Fatalf("%d forw:%d back:%d two:%d", i, lcslen(forw), lcslen(back), lcslen(two)) + } + if !two.valid() || !forw.valid() || !back.valid() { + t.Errorf("check failure") + } + } +} + +// TestDiffAPI tests the public API functions (Diff{Bytes,Strings,Runes}) +// to ensure at least minimal parity of the three representations. +func TestDiffAPI(t *testing.T) { + for _, test := range []struct { + a, b string + wantStrings, wantBytes, wantRunes string + }{ + {"abcXdef", "abcxdef", "[{3 4 3 4}]", "[{3 4 3 4}]", "[{3 4 3 4}]"}, // ASCII + {"abcฯ‰def", "abcฮฉdef", "[{3 5 3 5}]", "[{3 5 3 5}]", "[{3 4 3 4}]"}, // non-ASCII + } { + + gotStrings := fmt.Sprint(DiffStrings(test.a, test.b)) + if gotStrings != test.wantStrings { + t.Errorf("DiffStrings(%q, %q) = %v, want %v", + test.a, test.b, gotStrings, test.wantStrings) + } + gotBytes := fmt.Sprint(DiffBytes([]byte(test.a), []byte(test.b))) + if gotBytes != test.wantBytes { + t.Errorf("DiffBytes(%q, %q) = %v, want %v", + test.a, test.b, gotBytes, test.wantBytes) + } + gotRunes := fmt.Sprint(DiffRunes([]rune(test.a), []rune(test.b))) + if gotRunes != test.wantRunes { + t.Errorf("DiffRunes(%q, %q) = %v, want %v", + test.a, test.b, gotRunes, test.wantRunes) + } + } +} + +func BenchmarkTwoOld(b *testing.B) { + tests := genBench("abc", 96) + for i := 0; i < b.N; i++ { + for _, tt := range tests { + _, two := compute(stringSeqs{tt.before, tt.after}, twosided, 100) + if !two.valid() { + b.Error("check failed") + } + } + } +} + +func BenchmarkForwOld(b *testing.B) { + tests := genBench("abc", 96) + for i := 0; i < b.N; i++ { + for _, tt := range tests { + _, two := compute(stringSeqs{tt.before, tt.after}, forward, 100) + if !two.valid() { + b.Error("check failed") + } + } + } +} + +func genBench(set string, n int) []struct{ before, after string } { + // before and after for benchmarks. 24 strings of length n with + // before and after differing at least once, and about 5% + rand.Seed(3) + var ans []struct{ before, after string } + for i := 0; i < 24; i++ { + // maybe b should have an approximately known number of diffs + a := randstr(set, n) + cnt := 0 + bb := make([]rune, 0, n) + for _, r := range a { + if rand.Float64() < .05 { + cnt++ + r = 'N' + } + bb = append(bb, r) + } + if cnt == 0 { + // avoid == shortcut + bb[n/2] = 'N' + } + ans = append(ans, struct{ before, after string }{a, string(bb)}) + } + return ans +} + +// This benchmark represents a common case for a diff command: +// large file with a single relatively small diff in the middle. +// (It's not clear whether this is representative of gopls workloads +// or whether it is important to gopls diff performance.) +// +// TODO(adonovan) opt: it could be much faster. For example, +// comparing a file against itself is about 10x faster than with the +// small deletion in the middle. Strangely, comparing a file against +// itself minus the last byte is faster still; I don't know why. +// There is much low-hanging fruit here for further improvement. +func BenchmarkLargeFileSmallDiff(b *testing.B) { + data, err := os.ReadFile("old.go") // large file + if err != nil { + log.Fatal(err) + } + + n := len(data) + + src := string(data) + dst := src[:n*49/100] + src[n*51/100:] // remove 2% from the middle + b.Run("string", func(b *testing.B) { + for i := 0; i < b.N; i++ { + compute(stringSeqs{src, dst}, twosided, len(src)+len(dst)) + } + }) + + srcBytes := []byte(src) + dstBytes := []byte(dst) + b.Run("bytes", func(b *testing.B) { + for i := 0; i < b.N; i++ { + compute(bytesSeqs{srcBytes, dstBytes}, twosided, len(srcBytes)+len(dstBytes)) + } + }) + + srcRunes := []rune(src) + dstRunes := []rune(dst) + b.Run("runes", func(b *testing.B) { + for i := 0; i < b.N; i++ { + compute(runesSeqs{srcRunes, dstRunes}, twosided, len(srcRunes)+len(dstRunes)) + } + }) +} diff --git a/internal/x/tools/diff/lcs/sequence.go b/internal/x/tools/diff/lcs/sequence.go new file mode 100644 index 000000000000..2d72d2630435 --- /dev/null +++ b/internal/x/tools/diff/lcs/sequence.go @@ -0,0 +1,113 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lcs + +// This file defines the abstract sequence over which the LCS algorithm operates. + +// sequences abstracts a pair of sequences, A and B. +type sequences interface { + lengths() (int, int) // len(A), len(B) + commonPrefixLen(ai, aj, bi, bj int) int // len(commonPrefix(A[ai:aj], B[bi:bj])) + commonSuffixLen(ai, aj, bi, bj int) int // len(commonSuffix(A[ai:aj], B[bi:bj])) +} + +type stringSeqs struct{ a, b string } + +func (s stringSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s stringSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenString(s.a[ai:aj], s.b[bi:bj]) +} +func (s stringSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenString(s.a[ai:aj], s.b[bi:bj]) +} + +// The explicit capacity in s[i:j:j] leads to more efficient code. + +type bytesSeqs struct{ a, b []byte } + +func (s bytesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s bytesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} +func (s bytesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +type runesSeqs struct{ a, b []rune } + +func (s runesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s runesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} +func (s runesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +// TODO(adonovan): optimize these functions using ideas from: +// - https://go.dev/cl/408116 common.go +// - https://go.dev/cl/421435 xor_generic.go + +// TODO(adonovan): factor using generics when available, +// but measure performance impact. + +// commonPrefixLen* returns the length of the common prefix of a[ai:aj] and b[bi:bj]. +func commonPrefixLenBytes(a, b []byte) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} +func commonPrefixLenRunes(a, b []rune) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} +func commonPrefixLenString(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} + +// commonSuffixLen* returns the length of the common suffix of a[ai:aj] and b[bi:bj]. +func commonSuffixLenBytes(a, b []byte) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} +func commonSuffixLenRunes(a, b []rune) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} +func commonSuffixLenString(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} + +func min(x, y int) int { + if x < y { + return x + } else { + return y + } +} diff --git a/internal/x/tools/diff/myers/diff.go b/internal/x/tools/diff/myers/diff.go new file mode 100644 index 000000000000..4f280036a6d2 --- /dev/null +++ b/internal/x/tools/diff/myers/diff.go @@ -0,0 +1,246 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package myers implements the Myers diff algorithm. +package myers + +import ( + "strings" + + "github.com/golangci/golangci-lint/internal/x/tools/diff" +) + +// Sources: +// https://blog.jcoglan.com/2017/02/17/the-myers-diff-algorithm-part-3/ +// https://www.codeproject.com/Articles/42279/%2FArticles%2F42279%2FInvestigating-Myers-diff-algorithm-Part-1-of-2 + +// ComputeEdits returns the diffs of two strings using a simple +// line-based implementation, like [diff.Strings]. +// +// Deprecated: this implementation is moribund. However, when diffs +// appear in marker test expectations, they are the particular diffs +// produced by this implementation. The marker test framework +// asserts diff(orig, got)==wantDiff, but ideally it would compute +// got==apply(orig, wantDiff) so that the notation of the diff +// is immaterial. +func ComputeEdits(before, after string) []diff.Edit { + beforeLines := splitLines(before) + ops := operations(beforeLines, splitLines(after)) + + // Build a table mapping line number to offset. + lineOffsets := make([]int, 0, len(beforeLines)+1) + total := 0 + for i := range beforeLines { + lineOffsets = append(lineOffsets, total) + total += len(beforeLines[i]) + } + lineOffsets = append(lineOffsets, total) // EOF + + edits := make([]diff.Edit, 0, len(ops)) + for _, op := range ops { + start, end := lineOffsets[op.I1], lineOffsets[op.I2] + switch op.Kind { + case opDelete: + // Delete: before[I1:I2] is deleted. + edits = append(edits, diff.Edit{Start: start, End: end}) + case opInsert: + // Insert: after[J1:J2] is inserted at before[I1:I1]. + if content := strings.Join(op.Content, ""); content != "" { + edits = append(edits, diff.Edit{Start: start, End: end, New: content}) + } + } + } + return edits +} + +// opKind is used to denote the type of operation a line represents. +type opKind int + +const ( + opDelete opKind = iota // line deleted from input (-) + opInsert // line inserted into output (+) + opEqual // line present in input and output +) + +func (kind opKind) String() string { + switch kind { + case opDelete: + return "delete" + case opInsert: + return "insert" + case opEqual: + return "equal" + default: + panic("unknown opKind") + } +} + +type operation struct { + Kind opKind + Content []string // content from b + I1, I2 int // indices of the line in a + J1 int // indices of the line in b, J2 implied by len(Content) +} + +// operations returns the list of operations to convert a into b, consolidating +// operations for multiple lines and not including equal lines. +func operations(a, b []string) []*operation { + if len(a) == 0 && len(b) == 0 { + return nil + } + + trace, offset := shortestEditSequence(a, b) + snakes := backtrack(trace, len(a), len(b), offset) + + M, N := len(a), len(b) + + var i int + solution := make([]*operation, len(a)+len(b)) + + add := func(op *operation, i2, j2 int) { + if op == nil { + return + } + op.I2 = i2 + if op.Kind == opInsert { + op.Content = b[op.J1:j2] + } + solution[i] = op + i++ + } + x, y := 0, 0 + for _, snake := range snakes { + if len(snake) < 2 { + continue + } + var op *operation + // delete (horizontal) + for snake[0]-snake[1] > x-y { + if op == nil { + op = &operation{ + Kind: opDelete, + I1: x, + J1: y, + } + } + x++ + if x == M { + break + } + } + add(op, x, y) + op = nil + // insert (vertical) + for snake[0]-snake[1] < x-y { + if op == nil { + op = &operation{ + Kind: opInsert, + I1: x, + J1: y, + } + } + y++ + } + add(op, x, y) + op = nil + // equal (diagonal) + for x < snake[0] { + x++ + y++ + } + if x >= M && y >= N { + break + } + } + return solution[:i] +} + +// backtrack uses the trace for the edit sequence computation and returns the +// "snakes" that make up the solution. A "snake" is a single deletion or +// insertion followed by zero or diagonals. +func backtrack(trace [][]int, x, y, offset int) [][]int { + snakes := make([][]int, len(trace)) + d := len(trace) - 1 + for ; x > 0 && y > 0 && d > 0; d-- { + V := trace[d] + if len(V) == 0 { + continue + } + snakes[d] = []int{x, y} + + k := x - y + + var kPrev int + if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) { + kPrev = k + 1 + } else { + kPrev = k - 1 + } + + x = V[kPrev+offset] + y = x - kPrev + } + if x < 0 || y < 0 { + return snakes + } + snakes[d] = []int{x, y} + return snakes +} + +// shortestEditSequence returns the shortest edit sequence that converts a into b. +func shortestEditSequence(a, b []string) ([][]int, int) { + M, N := len(a), len(b) + V := make([]int, 2*(N+M)+1) + offset := N + M + trace := make([][]int, N+M+1) + + // Iterate through the maximum possible length of the SES (N+M). + for d := 0; d <= N+M; d++ { + copyV := make([]int, len(V)) + // k lines are represented by the equation y = x - k. We move in + // increments of 2 because end points for even d are on even k lines. + for k := -d; k <= d; k += 2 { + // At each point, we either go down or to the right. We go down if + // k == -d, and we go to the right if k == d. We also prioritize + // the maximum x value, because we prefer deletions to insertions. + var x int + if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) { + x = V[k+1+offset] // down + } else { + x = V[k-1+offset] + 1 // right + } + + y := x - k + + // Diagonal moves while we have equal contents. + for x < M && y < N && a[x] == b[y] { + x++ + y++ + } + + V[k+offset] = x + + // Return if we've exceeded the maximum values. + if x == M && y == N { + // Makes sure to save the state of the array before returning. + copy(copyV, V) + trace[d] = copyV + return trace, offset + } + } + + // Save the state of the array. + copy(copyV, V) + trace[d] = copyV + } + return nil, 0 +} + +func splitLines(text string) []string { + lines := strings.SplitAfter(text, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + return lines +} diff --git a/internal/x/tools/diff/ndiff.go b/internal/x/tools/diff/ndiff.go new file mode 100644 index 000000000000..f7aa2b79f6b4 --- /dev/null +++ b/internal/x/tools/diff/ndiff.go @@ -0,0 +1,99 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package diff + +import ( + "bytes" + "unicode/utf8" + + "github.com/golangci/golangci-lint/internal/x/tools/diff/lcs" +) + +// Strings computes the differences between two strings. +// The resulting edits respect rune boundaries. +func Strings(before, after string) []Edit { + if before == after { + return nil // common case + } + + if isASCII(before) && isASCII(after) { + // TODO(adonovan): opt: specialize diffASCII for strings. + return diffASCII([]byte(before), []byte(after)) + } + return diffRunes([]rune(before), []rune(after)) +} + +// Bytes computes the differences between two byte slices. +// The resulting edits respect rune boundaries. +func Bytes(before, after []byte) []Edit { + if bytes.Equal(before, after) { + return nil // common case + } + + if isASCII(before) && isASCII(after) { + return diffASCII(before, after) + } + return diffRunes(runes(before), runes(after)) +} + +func diffASCII(before, after []byte) []Edit { + diffs := lcs.DiffBytes(before, after) + + // Convert from LCS diffs. + res := make([]Edit, len(diffs)) + for i, d := range diffs { + res[i] = Edit{d.Start, d.End, string(after[d.ReplStart:d.ReplEnd])} + } + return res +} + +func diffRunes(before, after []rune) []Edit { + diffs := lcs.DiffRunes(before, after) + + // The diffs returned by the lcs package use indexes + // into whatever slice was passed in. + // Convert rune offsets to byte offsets. + res := make([]Edit, len(diffs)) + lastEnd := 0 + utf8Len := 0 + for i, d := range diffs { + utf8Len += runesLen(before[lastEnd:d.Start]) // text between edits + start := utf8Len + utf8Len += runesLen(before[d.Start:d.End]) // text deleted by this edit + res[i] = Edit{start, utf8Len, string(after[d.ReplStart:d.ReplEnd])} + lastEnd = d.End + } + return res +} + +// runes is like []rune(string(bytes)) without the duplicate allocation. +func runes(bytes []byte) []rune { + n := utf8.RuneCount(bytes) + runes := make([]rune, n) + for i := 0; i < n; i++ { + r, sz := utf8.DecodeRune(bytes) + bytes = bytes[sz:] + runes[i] = r + } + return runes +} + +// runesLen returns the length in bytes of the UTF-8 encoding of runes. +func runesLen(runes []rune) (len int) { + for _, r := range runes { + len += utf8.RuneLen(r) + } + return len +} + +// isASCII reports whether s contains only ASCII. +func isASCII[S string | []byte](s S) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} diff --git a/internal/x/tools/diff/readme.md b/internal/x/tools/diff/readme.md new file mode 100644 index 000000000000..4b979849897d --- /dev/null +++ b/internal/x/tools/diff/readme.md @@ -0,0 +1,8 @@ +# diff + +Extracted from `/internal/diff/` (related to `fixer`). +This is just a copy of the code without any changes. + +## History + +- sync with https://github.com/golang/tools/blob/v0.28.0 diff --git a/internal/x/tools/diff/unified.go b/internal/x/tools/diff/unified.go new file mode 100644 index 000000000000..cfbda61020a0 --- /dev/null +++ b/internal/x/tools/diff/unified.go @@ -0,0 +1,251 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package diff + +import ( + "fmt" + "log" + "strings" +) + +// DefaultContextLines is the number of unchanged lines of surrounding +// context displayed by Unified. Use ToUnified to specify a different value. +const DefaultContextLines = 3 + +// Unified returns a unified diff of the old and new strings. +// The old and new labels are the names of the old and new files. +// If the strings are equal, it returns the empty string. +func Unified(oldLabel, newLabel, old, new string) string { + edits := Strings(old, new) + unified, err := ToUnified(oldLabel, newLabel, old, edits, DefaultContextLines) + if err != nil { + // Can't happen: edits are consistent. + log.Fatalf("internal error in diff.Unified: %v", err) + } + return unified +} + +// ToUnified applies the edits to content and returns a unified diff, +// with contextLines lines of (unchanged) context around each diff hunk. +// The old and new labels are the names of the content and result files. +// It returns an error if the edits are inconsistent; see ApplyEdits. +func ToUnified(oldLabel, newLabel, content string, edits []Edit, contextLines int) (string, error) { + u, err := toUnified(oldLabel, newLabel, content, edits, contextLines) + if err != nil { + return "", err + } + return u.String(), nil +} + +// unified represents a set of edits as a unified diff. +type unified struct { + // from is the name of the original file. + from string + // to is the name of the modified file. + to string + // hunks is the set of edit hunks needed to transform the file content. + hunks []*hunk +} + +// Hunk represents a contiguous set of line edits to apply. +type hunk struct { + // The line in the original source where the hunk starts. + fromLine int + // The line in the original source where the hunk finishes. + toLine int + // The set of line based edits to apply. + lines []line +} + +// Line represents a single line operation to apply as part of a Hunk. +type line struct { + // kind is the type of line this represents, deletion, insertion or copy. + kind opKind + // content is the content of this line. + // For deletion it is the line being removed, for all others it is the line + // to put in the output. + content string +} + +// opKind is used to denote the type of operation a line represents. +type opKind int + +const ( + // opDelete is the operation kind for a line that is present in the input + // but not in the output. + opDelete opKind = iota + // opInsert is the operation kind for a line that is new in the output. + opInsert + // opEqual is the operation kind for a line that is the same in the input and + // output, often used to provide context around edited lines. + opEqual +) + +// String returns a human readable representation of an OpKind. It is not +// intended for machine processing. +func (k opKind) String() string { + switch k { + case opDelete: + return "delete" + case opInsert: + return "insert" + case opEqual: + return "equal" + default: + panic("unknown operation kind") + } +} + +// toUnified takes a file contents and a sequence of edits, and calculates +// a unified diff that represents those edits. +func toUnified(fromName, toName string, content string, edits []Edit, contextLines int) (unified, error) { + gap := contextLines * 2 + u := unified{ + from: fromName, + to: toName, + } + if len(edits) == 0 { + return u, nil + } + var err error + edits, err = lineEdits(content, edits) // expand to whole lines + if err != nil { + return u, err + } + lines := splitLines(content) + var h *hunk + last := 0 + toLine := 0 + for _, edit := range edits { + // Compute the zero-based line numbers of the edit start and end. + // TODO(adonovan): opt: compute incrementally, avoid O(n^2). + start := strings.Count(content[:edit.Start], "\n") + end := strings.Count(content[:edit.End], "\n") + if edit.End == len(content) && len(content) > 0 && content[len(content)-1] != '\n' { + end++ // EOF counts as an implicit newline + } + + switch { + case h != nil && start == last: + //direct extension + case h != nil && start <= last+gap: + //within range of previous lines, add the joiners + addEqualLines(h, lines, last, start) + default: + //need to start a new hunk + if h != nil { + // add the edge to the previous hunk + addEqualLines(h, lines, last, last+contextLines) + u.hunks = append(u.hunks, h) + } + toLine += start - last + h = &hunk{ + fromLine: start + 1, + toLine: toLine + 1, + } + // add the edge to the new hunk + delta := addEqualLines(h, lines, start-contextLines, start) + h.fromLine -= delta + h.toLine -= delta + } + last = start + for i := start; i < end; i++ { + h.lines = append(h.lines, line{kind: opDelete, content: lines[i]}) + last++ + } + if edit.New != "" { + for _, content := range splitLines(edit.New) { + h.lines = append(h.lines, line{kind: opInsert, content: content}) + toLine++ + } + } + } + if h != nil { + // add the edge to the final hunk + addEqualLines(h, lines, last, last+contextLines) + u.hunks = append(u.hunks, h) + } + return u, nil +} + +func splitLines(text string) []string { + lines := strings.SplitAfter(text, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + return lines +} + +func addEqualLines(h *hunk, lines []string, start, end int) int { + delta := 0 + for i := start; i < end; i++ { + if i < 0 { + continue + } + if i >= len(lines) { + return delta + } + h.lines = append(h.lines, line{kind: opEqual, content: lines[i]}) + delta++ + } + return delta +} + +// String converts a unified diff to the standard textual form for that diff. +// The output of this function can be passed to tools like patch. +func (u unified) String() string { + if len(u.hunks) == 0 { + return "" + } + b := new(strings.Builder) + fmt.Fprintf(b, "--- %s\n", u.from) + fmt.Fprintf(b, "+++ %s\n", u.to) + for _, hunk := range u.hunks { + fromCount, toCount := 0, 0 + for _, l := range hunk.lines { + switch l.kind { + case opDelete: + fromCount++ + case opInsert: + toCount++ + default: + fromCount++ + toCount++ + } + } + fmt.Fprint(b, "@@") + if fromCount > 1 { + fmt.Fprintf(b, " -%d,%d", hunk.fromLine, fromCount) + } else if hunk.fromLine == 1 && fromCount == 0 { + // Match odd GNU diff -u behavior adding to empty file. + fmt.Fprintf(b, " -0,0") + } else { + fmt.Fprintf(b, " -%d", hunk.fromLine) + } + if toCount > 1 { + fmt.Fprintf(b, " +%d,%d", hunk.toLine, toCount) + } else if hunk.toLine == 1 && toCount == 0 { + // Match odd GNU diff -u behavior adding to empty file. + fmt.Fprintf(b, " +0,0") + } else { + fmt.Fprintf(b, " +%d", hunk.toLine) + } + fmt.Fprint(b, " @@\n") + for _, l := range hunk.lines { + switch l.kind { + case opDelete: + fmt.Fprintf(b, "-%s", l.content) + case opInsert: + fmt.Fprintf(b, "+%s", l.content) + default: + fmt.Fprintf(b, " %s", l.content) + } + if !strings.HasSuffix(l.content, "\n") { + fmt.Fprintf(b, "\n\\ No newline at end of file\n") + } + } + } + return b.String() +} From e5eefefef4144040e695dcb3da2a3608775fa44d Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:29:34 +0100 Subject: [PATCH 15/57] feat: new fixer --- pkg/goanalysis/issue.go | 2 +- pkg/goanalysis/runner.go | 10 +- pkg/goanalysis/runners.go | 33 ++- pkg/goanalysis/runners_cache.go | 4 +- pkg/printers/json_test.go | 2 +- pkg/printers/testdata/golden-json.json | 2 +- pkg/printers/testdata/in-issues.json | 42 +-- pkg/result/issue.go | 23 +- pkg/result/processors/fixer.go | 343 ++++++++++++----------- pkg/result/processors/max_from_linter.go | 2 +- pkg/result/processors/max_same_issues.go | 2 +- pkg/result/processors/uniq_by_line.go | 2 +- test/output_test.go | 2 +- 13 files changed, 241 insertions(+), 228 deletions(-) diff --git a/pkg/goanalysis/issue.go b/pkg/goanalysis/issue.go index 15d8dd2b333f..854e7d15f0c1 100644 --- a/pkg/goanalysis/issue.go +++ b/pkg/goanalysis/issue.go @@ -26,7 +26,7 @@ type EncodingIssue struct { Severity string Pos token.Position LineRange *result.Range - Replacement *result.Replacement + SuggestedFixes []analysis.SuggestedFix ExpectNoLint bool ExpectedNoLintLinter string } diff --git a/pkg/goanalysis/runner.go b/pkg/goanalysis/runner.go index 4e04f958065d..3a8652486cea 100644 --- a/pkg/goanalysis/runner.go +++ b/pkg/goanalysis/runner.go @@ -42,6 +42,7 @@ type Diagnostic struct { Analyzer *analysis.Analyzer Position token.Position Pkg *packages.Package + File *token.File } type runner struct { @@ -312,17 +313,20 @@ func extractDiagnostics(roots []*action) (retDiags []Diagnostic, retErrors []err // We don't display a.Name/f.Category // as most users don't care. - posn := act.Package.Fset.Position(diag.Pos) - k := key{posn, act.Analyzer, diag.Message} + position := GetFilePositionFor(act.Package.Fset, diag.Pos) + file := act.Package.Fset.File(diag.Pos) + + k := key{Position: position, Analyzer: act.Analyzer, message: diag.Message} if seen[k] { continue // duplicate } seen[k] = true retDiag := Diagnostic{ + File: file, Diagnostic: diag, Analyzer: act.Analyzer, - Position: posn, + Position: position, Pkg: act.Package, } retDiags = append(retDiags, retDiag) diff --git a/pkg/goanalysis/runners.go b/pkg/goanalysis/runners.go index a9aee03a2bf8..644b9b542242 100644 --- a/pkg/goanalysis/runners.go +++ b/pkg/goanalysis/runners.go @@ -2,6 +2,7 @@ package goanalysis import ( "fmt" + "go/token" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" @@ -81,6 +82,7 @@ func runAnalyzers(cfg runAnalyzersConfig, lintCtx *linter.Context) ([]result.Iss func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) string) []result.Issue { var issues []result.Issue + for i := range diags { diag := &diags[i] linterName := linterNameBuilder(diag) @@ -92,11 +94,34 @@ func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) st text = fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message) } + var suggestedFixes []analysis.SuggestedFix + + for _, sf := range diag.SuggestedFixes { + nsf := analysis.SuggestedFix{Message: sf.Message} + + for _, edit := range sf.TextEdits { + end := edit.End + + if !end.IsValid() { + end = edit.Pos + } + + nsf.TextEdits = append(nsf.TextEdits, analysis.TextEdit{ + Pos: token.Pos(diag.File.Offset(edit.Pos)), + End: token.Pos(diag.File.Offset(end)), + NewText: edit.NewText, + }) + } + + suggestedFixes = append(suggestedFixes, nsf) + } + issues = append(issues, result.Issue{ - FromLinter: linterName, - Text: text, - Pos: diag.Position, - Pkg: diag.Pkg, + FromLinter: linterName, + Text: text, + Pos: diag.Position, + Pkg: diag.Pkg, + SuggestedFixes: suggestedFixes, }) if len(diag.Related) > 0 { diff --git a/pkg/goanalysis/runners_cache.go b/pkg/goanalysis/runners_cache.go index 8c244688b42d..4366155b02ae 100644 --- a/pkg/goanalysis/runners_cache.go +++ b/pkg/goanalysis/runners_cache.go @@ -48,7 +48,7 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages. Severity: i.Severity, Pos: i.Pos, LineRange: i.LineRange, - Replacement: i.Replacement, + SuggestedFixes: i.SuggestedFixes, ExpectNoLint: i.ExpectNoLint, ExpectedNoLintLinter: i.ExpectedNoLintLinter, }) @@ -123,7 +123,7 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context, Severity: issue.Severity, Pos: issue.Pos, LineRange: issue.LineRange, - Replacement: issue.Replacement, + SuggestedFixes: issue.SuggestedFixes, Pkg: pkg, ExpectNoLint: issue.ExpectNoLint, ExpectedNoLintLinter: issue.ExpectedNoLintLinter, diff --git a/pkg/printers/json_test.go b/pkg/printers/json_test.go index 98d21e908d9e..a28dbcf35369 100644 --- a/pkg/printers/json_test.go +++ b/pkg/printers/json_test.go @@ -49,7 +49,7 @@ func TestJSON_Print(t *testing.T) { err := printer.Print(issues) require.NoError(t, err) - expected := `{"Issues":[{"FromLinter":"linter-a","Text":"some issue","Severity":"warning","SourceLines":null,"Replacement":null,"Pos":{"Filename":"path/to/filea.go","Offset":2,"Line":10,"Column":4},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"linter-b","Text":"another issue","Severity":"error","SourceLines":["func foo() {","\tfmt.Println(\"bar\")","}"],"Replacement":null,"Pos":{"Filename":"path/to/fileb.go","Offset":5,"Line":300,"Column":9},"ExpectNoLint":false,"ExpectedNoLintLinter":""}],"Report":null} + expected := `{"Issues":[{"FromLinter":"linter-a","Text":"some issue","Severity":"warning","SourceLines":null,"Pos":{"Filename":"path/to/filea.go","Offset":2,"Line":10,"Column":4},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"linter-b","Text":"another issue","Severity":"error","SourceLines":["func foo() {","\tfmt.Println(\"bar\")","}"],"Pos":{"Filename":"path/to/fileb.go","Offset":5,"Line":300,"Column":9},"ExpectNoLint":false,"ExpectedNoLintLinter":""}],"Report":null} ` assert.Equal(t, expected, buf.String()) diff --git a/pkg/printers/testdata/golden-json.json b/pkg/printers/testdata/golden-json.json index 92ba51b946ef..880b0dcc8074 100644 --- a/pkg/printers/testdata/golden-json.json +++ b/pkg/printers/testdata/golden-json.json @@ -1 +1 @@ -{"Issues":[{"FromLinter":"gochecknoinits","Text":"don't use `init` function","Severity":"","SourceLines":["func init() {"],"Replacement":null,"Pos":{"Filename":"pkg/experimental/myplugin/myplugin.go","Offset":162,"Line":13,"Column":1},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"gocritic","Text":"hugeParam: settings is heavy (80 bytes); consider passing it by pointer","Severity":"","SourceLines":["func (b *PluginBuilder) loadConfig(cfg *config.Config, name string, settings config.CustomLinterSettings) (*linter.Config, error) {"],"Replacement":null,"Pos":{"Filename":"pkg/lint/lintersdb/builder_plugin.go","Offset":1480,"Line":59,"Column":69},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"goimports","Text":"File is not `goimports`-ed with -local github.com/golangci/golangci-lint","Severity":"","SourceLines":[""],"Replacement":{"NeedOnlyDelete":false,"NewLines":["","\t\"github.com/stretchr/testify/require\"",""],"Inline":null},"Pos":{"Filename":"pkg/printers/printer_test.go","Offset":0,"Line":6,"Column":0},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 144 bytes could be of size 128 bytes","Severity":"","SourceLines":["type Issues struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/issues.go","Offset":3338,"Line":107,"Column":13},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 3144 bytes could be of size 3096 bytes","Severity":"","SourceLines":["type LintersSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":4576,"Line":200,"Column":22},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 72 bytes could be of size 64 bytes","Severity":"","SourceLines":["type ExhaustiveSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":10829,"Line":383,"Column":25},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 72 bytes could be of size 56 bytes","Severity":"","SourceLines":["type GoConstSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":14399,"Line":470,"Column":22},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 136 bytes could be of size 128 bytes","Severity":"","SourceLines":["type GoCriticSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":14934,"Line":482,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 64 bytes could be of size 56 bytes","Severity":"","SourceLines":["type GosmopolitanSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":18601,"Line":584,"Column":27},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 88 bytes could be of size 80 bytes","Severity":"","SourceLines":["type GovetSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":18867,"Line":591,"Column":20},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 40 bytes could be of size 32 bytes","Severity":"","SourceLines":["type NoLintLintSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":22337,"Line":710,"Column":25},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 112 bytes could be of size 104 bytes","Severity":"","SourceLines":["type ReviveSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":24019,"Line":762,"Column":21},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 32 bytes could be of size 24 bytes","Severity":"","SourceLines":["type SlogLintSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":24648,"Line":787,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 40 bytes could be of size 32 bytes","Severity":"","SourceLines":["type TagAlignSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":25936,"Line":817,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 80 bytes could be of size 72 bytes","Severity":"","SourceLines":["type VarnamelenSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":28758,"Line":902,"Column":25},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 112 bytes could be of size 96 bytes","Severity":"","SourceLines":["type WSLSettings struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":29898,"Line":928,"Column":18},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 168 bytes could be of size 160 bytes","Severity":"","SourceLines":["type Run struct {"],"Replacement":null,"Pos":{"Filename":"pkg/config/run.go","Offset":112,"Line":6,"Column":10},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 128 bytes could be of size 120 bytes","Severity":"","SourceLines":["type Config struct {"],"Replacement":null,"Pos":{"Filename":"pkg/lint/linter/config.go","Offset":1329,"Line":36,"Column":13},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 96 bytes could be of size 88 bytes","Severity":"","SourceLines":["\tfor _, tc := range []struct {"],"Replacement":null,"Pos":{"Filename":"pkg/golinters/govet_test.go","Offset":1804,"Line":70,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 64 bytes could be of size 56 bytes","Severity":"","SourceLines":["type Diff struct {"],"Replacement":null,"Pos":{"Filename":"pkg/result/processors/diff.go","Offset":233,"Line":17,"Column":11},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"revive","Text":"unused-parameter: parameter 'pass' seems to be unused, consider removing or renaming it as _","Severity":"warning","SourceLines":["\t\t\tRun: func(pass *analysis.Pass) (any, error) {"],"Replacement":null,"LineRange":{"From":49,"To":49},"Pos":{"Filename":"pkg/experimental/myplugin/myplugin.go","Offset":921,"Line":49,"Column":14},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"unused","Text":"const `defaultFileMode` is unused","Severity":"","SourceLines":["const defaultFileMode = 0644"],"Replacement":null,"Pos":{"Filename":"pkg/commands/run.go","Offset":1209,"Line":47,"Column":7},"ExpectNoLint":false,"ExpectedNoLintLinter":""}],"Report":{"Warnings":[{"Tag":"runner","Text":"The linter 'maligned' is deprecated (since v1.38.0) due to: The repository of the linter has been archived by the owner. Replaced by govet 'fieldalignment'."}],"Linters":[{"Name":"asasalint"},{"Name":"asciicheck"},{"Name":"bidichk"},{"Name":"bodyclose","Enabled":true},{"Name":"containedctx"},{"Name":"contextcheck"},{"Name":"cyclop"},{"Name":"decorder"},{"Name":"deadcode"},{"Name":"depguard","Enabled":true},{"Name":"dogsled","Enabled":true},{"Name":"dupl","Enabled":true},{"Name":"dupword"},{"Name":"durationcheck"},{"Name":"errcheck","Enabled":true,"EnabledByDefault":true},{"Name":"errchkjson"},{"Name":"errname"},{"Name":"errorlint","Enabled":true},{"Name":"execinquery"},{"Name":"exhaustive"},{"Name":"exhaustivestruct"},{"Name":"exhaustruct"},{"Name":"exportloopref","Enabled":true},{"Name":"forbidigo"},{"Name":"forcetypeassert"},{"Name":"funlen","Enabled":true},{"Name":"gci"},{"Name":"ginkgolinter"},{"Name":"gocheckcompilerdirectives","Enabled":true},{"Name":"gochecknoglobals"},{"Name":"gochecknoinits","Enabled":true},{"Name":"gochecksumtype"},{"Name":"gocognit"},{"Name":"goconst","Enabled":true},{"Name":"gocritic","Enabled":true},{"Name":"gocyclo","Enabled":true},{"Name":"godot"},{"Name":"godox"},{"Name":"err113"},{"Name":"gofmt","Enabled":true},{"Name":"gofumpt"},{"Name":"goheader"},{"Name":"goimports","Enabled":true},{"Name":"golint"},{"Name":"mnd","Enabled":true},{"Name":"gomoddirectives"},{"Name":"gomodguard"},{"Name":"goprintffuncname","Enabled":true},{"Name":"gosec","Enabled":true},{"Name":"gosimple","Enabled":true,"EnabledByDefault":true},{"Name":"gosmopolitan"},{"Name":"govet","Enabled":true,"EnabledByDefault":true},{"Name":"grouper"},{"Name":"ifshort"},{"Name":"importas"},{"Name":"inamedparam"},{"Name":"ineffassign","Enabled":true,"EnabledByDefault":true},{"Name":"interfacebloat"},{"Name":"interfacer"},{"Name":"ireturn"},{"Name":"lll","Enabled":true},{"Name":"loggercheck"},{"Name":"maintidx"},{"Name":"makezero"},{"Name":"maligned","Enabled":true},{"Name":"mirror"},{"Name":"misspell","Enabled":true},{"Name":"musttag"},{"Name":"nakedret","Enabled":true},{"Name":"nestif"},{"Name":"nilerr"},{"Name":"nilnil"},{"Name":"nlreturn"},{"Name":"noctx","Enabled":true},{"Name":"nonamedreturns"},{"Name":"nosnakecase"},{"Name":"nosprintfhostport"},{"Name":"paralleltest"},{"Name":"perfsprint"},{"Name":"prealloc"},{"Name":"predeclared"},{"Name":"promlinter"},{"Name":"protogetter"},{"Name":"reassign"},{"Name":"revive","Enabled":true},{"Name":"rowserrcheck"},{"Name":"sloglint"},{"Name":"scopelint"},{"Name":"sqlclosecheck"},{"Name":"spancheck"},{"Name":"staticcheck","Enabled":true,"EnabledByDefault":true},{"Name":"structcheck"},{"Name":"stylecheck","Enabled":true},{"Name":"tagalign"},{"Name":"tagliatelle"},{"Name":"tenv"},{"Name":"testableexamples"},{"Name":"testifylint"},{"Name":"testpackage"},{"Name":"thelper"},{"Name":"tparallel"},{"Name":"typecheck","Enabled":true,"EnabledByDefault":true},{"Name":"unconvert","Enabled":true},{"Name":"unparam","Enabled":true},{"Name":"unused","Enabled":true,"EnabledByDefault":true},{"Name":"usestdlibvars"},{"Name":"varcheck"},{"Name":"varnamelen"},{"Name":"wastedassign"},{"Name":"whitespace","Enabled":true},{"Name":"wrapcheck"},{"Name":"wsl"},{"Name":"zerologlint"},{"Name":"nolintlint","Enabled":true}]}} +{"Issues":[{"FromLinter":"gochecknoinits","Text":"don't use `init` function","Severity":"","SourceLines":["func init() {"],"Pos":{"Filename":"pkg/experimental/myplugin/myplugin.go","Offset":162,"Line":13,"Column":1},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"gocritic","Text":"hugeParam: settings is heavy (80 bytes); consider passing it by pointer","Severity":"","SourceLines":["func (b *PluginBuilder) loadConfig(cfg *config.Config, name string, settings config.CustomLinterSettings) (*linter.Config, error) {"],"Pos":{"Filename":"pkg/lint/lintersdb/builder_plugin.go","Offset":1480,"Line":59,"Column":69},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"goimports","Text":"File is not `goimports`-ed with -local github.com/golangci/golangci-lint","Severity":"","SourceLines":[""],"Pos":{"Filename":"pkg/printers/printer_test.go","Offset":0,"Line":6,"Column":0},"SuggestedFixes":[{"Message":"","TextEdits":[{"Pos":13,"End":32,"NewText":null}]}],"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 144 bytes could be of size 128 bytes","Severity":"","SourceLines":["type Issues struct {"],"Pos":{"Filename":"pkg/config/issues.go","Offset":3338,"Line":107,"Column":13},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 3144 bytes could be of size 3096 bytes","Severity":"","SourceLines":["type LintersSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":4576,"Line":200,"Column":22},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 72 bytes could be of size 64 bytes","Severity":"","SourceLines":["type ExhaustiveSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":10829,"Line":383,"Column":25},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 72 bytes could be of size 56 bytes","Severity":"","SourceLines":["type GoConstSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":14399,"Line":470,"Column":22},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 136 bytes could be of size 128 bytes","Severity":"","SourceLines":["type GoCriticSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":14934,"Line":482,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 64 bytes could be of size 56 bytes","Severity":"","SourceLines":["type GosmopolitanSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":18601,"Line":584,"Column":27},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 88 bytes could be of size 80 bytes","Severity":"","SourceLines":["type GovetSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":18867,"Line":591,"Column":20},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 40 bytes could be of size 32 bytes","Severity":"","SourceLines":["type NoLintLintSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":22337,"Line":710,"Column":25},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 112 bytes could be of size 104 bytes","Severity":"","SourceLines":["type ReviveSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":24019,"Line":762,"Column":21},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 32 bytes could be of size 24 bytes","Severity":"","SourceLines":["type SlogLintSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":24648,"Line":787,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 40 bytes could be of size 32 bytes","Severity":"","SourceLines":["type TagAlignSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":25936,"Line":817,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 80 bytes could be of size 72 bytes","Severity":"","SourceLines":["type VarnamelenSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":28758,"Line":902,"Column":25},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 112 bytes could be of size 96 bytes","Severity":"","SourceLines":["type WSLSettings struct {"],"Pos":{"Filename":"pkg/config/linters_settings.go","Offset":29898,"Line":928,"Column":18},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 168 bytes could be of size 160 bytes","Severity":"","SourceLines":["type Run struct {"],"Pos":{"Filename":"pkg/config/run.go","Offset":112,"Line":6,"Column":10},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 128 bytes could be of size 120 bytes","Severity":"","SourceLines":["type Config struct {"],"Pos":{"Filename":"pkg/lint/linter/config.go","Offset":1329,"Line":36,"Column":13},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 96 bytes could be of size 88 bytes","Severity":"","SourceLines":["\tfor _, tc := range []struct {"],"Pos":{"Filename":"pkg/golinters/govet_test.go","Offset":1804,"Line":70,"Column":23},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"maligned","Text":"struct of size 64 bytes could be of size 56 bytes","Severity":"","SourceLines":["type Diff struct {"],"Pos":{"Filename":"pkg/result/processors/diff.go","Offset":233,"Line":17,"Column":11},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"revive","Text":"unused-parameter: parameter 'pass' seems to be unused, consider removing or renaming it as _","Severity":"warning","SourceLines":["\t\t\tRun: func(pass *analysis.Pass) (any, error) {"],"Pos":{"Filename":"pkg/experimental/myplugin/myplugin.go","Offset":921,"Line":49,"Column":14},"LineRange":{"From":49,"To":49},"ExpectNoLint":false,"ExpectedNoLintLinter":""},{"FromLinter":"unused","Text":"const `defaultFileMode` is unused","Severity":"","SourceLines":["const defaultFileMode = 0644"],"Pos":{"Filename":"pkg/commands/run.go","Offset":1209,"Line":47,"Column":7},"ExpectNoLint":false,"ExpectedNoLintLinter":""}],"Report":{"Warnings":[{"Tag":"runner","Text":"The linter 'maligned' is deprecated (since v1.38.0) due to: The repository of the linter has been archived by the owner. Replaced by govet 'fieldalignment'."}],"Linters":[{"Name":"asasalint"},{"Name":"asciicheck"},{"Name":"bidichk"},{"Name":"bodyclose","Enabled":true},{"Name":"containedctx"},{"Name":"contextcheck"},{"Name":"cyclop"},{"Name":"decorder"},{"Name":"deadcode"},{"Name":"depguard","Enabled":true},{"Name":"dogsled","Enabled":true},{"Name":"dupl","Enabled":true},{"Name":"dupword"},{"Name":"durationcheck"},{"Name":"errcheck","Enabled":true,"EnabledByDefault":true},{"Name":"errchkjson"},{"Name":"errname"},{"Name":"errorlint","Enabled":true},{"Name":"execinquery"},{"Name":"exhaustive"},{"Name":"exhaustivestruct"},{"Name":"exhaustruct"},{"Name":"exportloopref","Enabled":true},{"Name":"forbidigo"},{"Name":"forcetypeassert"},{"Name":"funlen","Enabled":true},{"Name":"gci"},{"Name":"ginkgolinter"},{"Name":"gocheckcompilerdirectives","Enabled":true},{"Name":"gochecknoglobals"},{"Name":"gochecknoinits","Enabled":true},{"Name":"gochecksumtype"},{"Name":"gocognit"},{"Name":"goconst","Enabled":true},{"Name":"gocritic","Enabled":true},{"Name":"gocyclo","Enabled":true},{"Name":"godot"},{"Name":"godox"},{"Name":"err113"},{"Name":"gofmt","Enabled":true},{"Name":"gofumpt"},{"Name":"goheader"},{"Name":"goimports","Enabled":true},{"Name":"golint"},{"Name":"mnd","Enabled":true},{"Name":"gomoddirectives"},{"Name":"gomodguard"},{"Name":"goprintffuncname","Enabled":true},{"Name":"gosec","Enabled":true},{"Name":"gosimple","Enabled":true,"EnabledByDefault":true},{"Name":"gosmopolitan"},{"Name":"govet","Enabled":true,"EnabledByDefault":true},{"Name":"grouper"},{"Name":"ifshort"},{"Name":"importas"},{"Name":"inamedparam"},{"Name":"ineffassign","Enabled":true,"EnabledByDefault":true},{"Name":"interfacebloat"},{"Name":"interfacer"},{"Name":"ireturn"},{"Name":"lll","Enabled":true},{"Name":"loggercheck"},{"Name":"maintidx"},{"Name":"makezero"},{"Name":"maligned","Enabled":true},{"Name":"mirror"},{"Name":"misspell","Enabled":true},{"Name":"musttag"},{"Name":"nakedret","Enabled":true},{"Name":"nestif"},{"Name":"nilerr"},{"Name":"nilnil"},{"Name":"nlreturn"},{"Name":"noctx","Enabled":true},{"Name":"nonamedreturns"},{"Name":"nosnakecase"},{"Name":"nosprintfhostport"},{"Name":"paralleltest"},{"Name":"perfsprint"},{"Name":"prealloc"},{"Name":"predeclared"},{"Name":"promlinter"},{"Name":"protogetter"},{"Name":"reassign"},{"Name":"revive","Enabled":true},{"Name":"rowserrcheck"},{"Name":"sloglint"},{"Name":"scopelint"},{"Name":"sqlclosecheck"},{"Name":"spancheck"},{"Name":"staticcheck","Enabled":true,"EnabledByDefault":true},{"Name":"structcheck"},{"Name":"stylecheck","Enabled":true},{"Name":"tagalign"},{"Name":"tagliatelle"},{"Name":"tenv"},{"Name":"testableexamples"},{"Name":"testifylint"},{"Name":"testpackage"},{"Name":"thelper"},{"Name":"tparallel"},{"Name":"typecheck","Enabled":true,"EnabledByDefault":true},{"Name":"unconvert","Enabled":true},{"Name":"unparam","Enabled":true},{"Name":"unused","Enabled":true,"EnabledByDefault":true},{"Name":"usestdlibvars"},{"Name":"varcheck"},{"Name":"varnamelen"},{"Name":"wastedassign"},{"Name":"whitespace","Enabled":true},{"Name":"wrapcheck"},{"Name":"wsl"},{"Name":"zerologlint"},{"Name":"nolintlint","Enabled":true}]}} diff --git a/pkg/printers/testdata/in-issues.json b/pkg/printers/testdata/in-issues.json index 86378851b678..1267d11b0f6c 100644 --- a/pkg/printers/testdata/in-issues.json +++ b/pkg/printers/testdata/in-issues.json @@ -6,7 +6,6 @@ "SourceLines": [ "func init() {" ], - "Replacement": null, "Pos": { "Filename": "pkg/experimental/myplugin/myplugin.go", "Offset": 162, @@ -23,7 +22,6 @@ "SourceLines": [ "func (b *PluginBuilder) loadConfig(cfg *config.Config, name string, settings config.CustomLinterSettings) (*linter.Config, error) {" ], - "Replacement": null, "Pos": { "Filename": "pkg/lint/lintersdb/builder_plugin.go", "Offset": 1480, @@ -40,15 +38,18 @@ "SourceLines": [ "" ], - "Replacement": { - "NeedOnlyDelete": false, - "NewLines": [ - "", - "\t\"github.com/stretchr/testify/require\"", - "" - ], - "Inline": null - }, + "SuggestedFixes": [ + { + "Message": "", + "TextEdits": [ + { + "Pos": 13, + "End": 32, + "NewText": null + } + ] + } + ], "Pos": { "Filename": "pkg/printers/printer_test.go", "Offset": 0, @@ -65,7 +66,6 @@ "SourceLines": [ "type Issues struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/issues.go", "Offset": 3338, @@ -82,7 +82,6 @@ "SourceLines": [ "type LintersSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 4576, @@ -99,7 +98,6 @@ "SourceLines": [ "type ExhaustiveSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 10829, @@ -116,7 +114,6 @@ "SourceLines": [ "type GoConstSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 14399, @@ -133,7 +130,6 @@ "SourceLines": [ "type GoCriticSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 14934, @@ -150,7 +146,6 @@ "SourceLines": [ "type GosmopolitanSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 18601, @@ -167,7 +162,6 @@ "SourceLines": [ "type GovetSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 18867, @@ -184,7 +178,6 @@ "SourceLines": [ "type NoLintLintSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 22337, @@ -201,7 +194,6 @@ "SourceLines": [ "type ReviveSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 24019, @@ -218,7 +210,6 @@ "SourceLines": [ "type SlogLintSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 24648, @@ -235,7 +226,6 @@ "SourceLines": [ "type TagAlignSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 25936, @@ -252,7 +242,6 @@ "SourceLines": [ "type VarnamelenSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 28758, @@ -269,7 +258,6 @@ "SourceLines": [ "type WSLSettings struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/linters_settings.go", "Offset": 29898, @@ -286,7 +274,6 @@ "SourceLines": [ "type Run struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/config/run.go", "Offset": 112, @@ -303,7 +290,6 @@ "SourceLines": [ "type Config struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/lint/linter/config.go", "Offset": 1329, @@ -320,7 +306,6 @@ "SourceLines": [ "\tfor _, tc := range []struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/golinters/govet_test.go", "Offset": 1804, @@ -337,7 +322,6 @@ "SourceLines": [ "type Diff struct {" ], - "Replacement": null, "Pos": { "Filename": "pkg/result/processors/diff.go", "Offset": 233, @@ -354,7 +338,6 @@ "SourceLines": [ "\t\t\tRun: func(pass *analysis.Pass) (any, error) {" ], - "Replacement": null, "LineRange": { "From": 49, "To": 49 @@ -375,7 +358,6 @@ "SourceLines": [ "const defaultFileMode = 0644" ], - "Replacement": null, "Pos": { "Filename": "pkg/commands/run.go", "Offset": 1209, diff --git a/pkg/result/issue.go b/pkg/result/issue.go index 32246a6df43a..e338963fa3d3 100644 --- a/pkg/result/issue.go +++ b/pkg/result/issue.go @@ -5,6 +5,7 @@ import ( "fmt" "go/token" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" ) @@ -12,18 +13,6 @@ type Range struct { From, To int } -type Replacement struct { - NeedOnlyDelete bool // need to delete all lines of the issue without replacement with new lines - NewLines []string // if NeedDelete is false it's the replacement lines - Inline *InlineFix -} - -type InlineFix struct { - StartCol int // zero-based - Length int // length of chunk to be replaced - NewString string -} - type Issue struct { FromLinter string Text string @@ -33,19 +22,19 @@ type Issue struct { // Source lines of a code with the issue to show SourceLines []string - // If we know how to fix the issue we can provide replacement lines - Replacement *Replacement - // Pkg is needed for proper caching of linting results Pkg *packages.Package `json:"-"` - LineRange *Range `json:",omitempty"` - Pos token.Position + LineRange *Range `json:",omitempty"` + // HunkPos is used only when golangci-lint is run over a diff HunkPos int `json:",omitempty"` + // If we know how to fix the issue we can provide replacement lines + SuggestedFixes []analysis.SuggestedFix `json:",omitempty"` + // If we are expecting a nolint (because this is from nolintlint), record the expected linter ExpectNoLint bool ExpectedNoLintLinter string diff --git a/pkg/result/processors/fixer.go b/pkg/result/processors/fixer.go index 764af5a9231e..084655f7758e 100644 --- a/pkg/result/processors/fixer.go +++ b/pkg/result/processors/fixer.go @@ -1,14 +1,22 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file is inspired by go/analysis/internal/checker/checker.go + package processors import ( - "bytes" + "errors" "fmt" + "go/format" + "go/token" "os" - "path/filepath" - "sort" - "strings" + "slices" + + "golang.org/x/exp/maps" - "github.com/golangci/golangci-lint/internal/go/robustio" + "github.com/golangci/golangci-lint/internal/x/tools/diff" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/logutils" @@ -18,6 +26,8 @@ import ( var _ Processor = (*Fixer)(nil) +const filePerm = 0644 + type Fixer struct { cfg *config.Config log logutils.Log @@ -43,218 +53,221 @@ func (p Fixer) Process(issues []result.Issue) ([]result.Issue, error) { return issues, nil } - outIssues := make([]result.Issue, 0, len(issues)) - issuesToFixPerFile := map[string][]result.Issue{} - for i := range issues { - issue := &issues[i] - if issue.Replacement == nil { - outIssues = append(outIssues, *issue) - continue - } - - issuesToFixPerFile[issue.FilePath()] = append(issuesToFixPerFile[issue.FilePath()], *issue) - } - - for file, issuesToFix := range issuesToFixPerFile { - err := p.sw.TrackStageErr("all", func() error { - return p.fixIssuesInFile(file, issuesToFix) - }) - if err != nil { - p.log.Errorf("Failed to fix issues in file %s: %s", file, err) + p.log.Infof("Applying suggested fixes") - // show issues only if can't fix them - outIssues = append(outIssues, issuesToFix...) - } + notFixableIssues, err := timeutils.TrackStage(p.sw, "all", func() ([]result.Issue, error) { + return p.process(issues) + }) + if err != nil { + p.log.Errorf("Failed to fix issues: %v", err) } p.printStat() - return outIssues, nil + return notFixableIssues, nil } -func (Fixer) Finish() {} +//nolint:funlen,gocyclo // This function should not be split. +func (p Fixer) process(issues []result.Issue) ([]result.Issue, error) { + // filenames / linters / edits + editsByLinter := make(map[string]map[string][]diff.Edit) -func (p Fixer) fixIssuesInFile(filePath string, issues []result.Issue) error { - // TODO: don't read the whole file into memory: read line by line; - // can't just use bufio.scanner: it has a line length limit - origFileData, err := p.fileCache.GetFileBytes(filePath) - if err != nil { - return fmt.Errorf("failed to get file bytes for %s: %w", filePath, err) - } + var notFixableIssues []result.Issue - origFileLines := bytes.Split(origFileData, []byte("\n")) + for i := range issues { + issue := issues[i] - tmpFileName := filepath.Join(filepath.Dir(filePath), fmt.Sprintf(".%s.golangci_fix", filepath.Base(filePath))) + if issue.SuggestedFixes == nil || skipTextEditWithoutPosition(&issue) { + notFixableIssues = append(notFixableIssues, issue) + continue + } - tmpOutFile, err := os.Create(tmpFileName) - if err != nil { - return fmt.Errorf("failed to make file %s: %w", tmpFileName, err) + for _, sf := range issue.SuggestedFixes { + for _, edit := range sf.TextEdits { + start, end := edit.Pos, edit.End + if start > end { + return nil, fmt.Errorf("%q suggests invalid fix: pos (%v) > end (%v)", + issue.FromLinter, edit.Pos, edit.End) + } + + edit := diff.Edit{ + Start: int(start), + End: int(end), + New: string(edit.NewText), + } + + if _, ok := editsByLinter[issue.FilePath()]; !ok { + editsByLinter[issue.FilePath()] = make(map[string][]diff.Edit) + } + + editsByLinter[issue.FilePath()][issue.FromLinter] = append(editsByLinter[issue.FilePath()][issue.FromLinter], edit) + } + } } - // merge multiple issues per line into one issue - issuesPerLine := map[int][]result.Issue{} - for i := range issues { - issue := &issues[i] - issuesPerLine[issue.Line()] = append(issuesPerLine[issue.Line()], *issue) - } + // Validate and group the edits to each actual file. + editsByPath := make(map[string][]diff.Edit) + for path, linterToEdits := range editsByLinter { + excludedLinters := make(map[string]struct{}) - issues = issues[:0] // reuse the same memory - for line, lineIssues := range issuesPerLine { - if mergedIssue := p.mergeLineIssues(line, lineIssues, origFileLines); mergedIssue != nil { - issues = append(issues, *mergedIssue) + linters := maps.Keys(linterToEdits) + + // Does any linter create conflicting edits? + for _, linter := range linters { + edits := linterToEdits[linter] + if _, invalid := validateEdits(edits); invalid > 0 { + name, x, y := linter, edits[invalid-1], edits[invalid] + excludedLinters[name] = struct{}{} + + err := diff3Conflict(path, name, name, []diff.Edit{x}, []diff.Edit{y}) + // TODO(ldez) TUI? + p.log.Warnf("Changes related to %q are skipped for the file %q: %v", + name, path, err) + } } - } - issues = p.findNotIntersectingIssues(issues) + // Does any pair of different linters create edits that conflict? + for j := range linters { + for k := range linters[:j] { + x, y := linters[j], linters[k] + if x > y { + x, y = y, x + } - if err = p.writeFixedFile(origFileLines, issues, tmpOutFile); err != nil { - tmpOutFile.Close() - _ = robustio.RemoveAll(tmpOutFile.Name()) - return err - } + _, foundX := excludedLinters[x] + _, foundY := excludedLinters[y] + if foundX || foundY { + continue + } - tmpOutFile.Close() + xedits, yedits := linterToEdits[x], linterToEdits[y] - if err = robustio.Rename(tmpOutFile.Name(), filePath); err != nil { - _ = robustio.RemoveAll(tmpOutFile.Name()) - return fmt.Errorf("failed to rename %s -> %s: %w", tmpOutFile.Name(), filePath, err) - } + combined := slices.Concat(xedits, yedits) - return nil -} + if _, invalid := validateEdits(combined); invalid > 0 { + excludedLinters[x] = struct{}{} + p.log.Warnf("Changes related to %q are skipped for the file %q due to conflicts with %q.", x, path, y) + } + } + } -func (p Fixer) mergeLineIssues(lineNum int, lineIssues []result.Issue, origFileLines [][]byte) *result.Issue { - origLine := origFileLines[lineNum-1] // lineNum is 1-based + var edits []diff.Edit + for linter := range linterToEdits { + if _, found := excludedLinters[linter]; !found { + edits = append(edits, linterToEdits[linter]...) + } + } - if len(lineIssues) == 1 && lineIssues[0].Replacement.Inline == nil { - return &lineIssues[0] + editsByPath[path], _ = validateEdits(edits) // remove duplicates. already validated. } - // check issues first - for ind := range lineIssues { - li := &lineIssues[ind] + var editError error - if li.LineRange != nil { - p.log.Infof("Line %d has multiple issues but at least one of them is ranged: %#v", lineNum, lineIssues) - return &lineIssues[0] + // Now we've got a set of valid edits for each file. Apply them. + for path, edits := range editsByPath { + contents, err := p.fileCache.GetFileBytes(path) + if err != nil { + editError = errors.Join(editError, err) + continue } - inline := li.Replacement.Inline + out, err := diff.ApplyBytes(contents, edits) + if err != nil { + editError = errors.Join(editError, err) + continue + } - if inline == nil || len(li.Replacement.NewLines) != 0 || li.Replacement.NeedOnlyDelete { - p.log.Infof("Line %d has multiple issues but at least one of them isn't inline: %#v", lineNum, lineIssues) - return li + // Try to format the file. + if formatted, err := format.Source(out); err == nil { + out = formatted } - if inline.StartCol < 0 || inline.Length <= 0 || inline.StartCol+inline.Length > len(origLine) { - p.log.Warnf("Line %d (%q) has invalid inline fix: %#v, %#v", lineNum, origLine, li, inline) - return nil + if err := os.WriteFile(path, out, filePerm); err != nil { + editError = errors.Join(editError, err) + continue } } - return p.applyInlineFixes(lineIssues, origLine, lineNum) + return notFixableIssues, editError } -func (p Fixer) applyInlineFixes(lineIssues []result.Issue, origLine []byte, lineNum int) *result.Issue { - sort.Slice(lineIssues, func(i, j int) bool { - return lineIssues[i].Replacement.Inline.StartCol < lineIssues[j].Replacement.Inline.StartCol - }) - - var newLineBuf bytes.Buffer - newLineBuf.Grow(len(origLine)) - - //nolint:misspell // misspelling is intentional - // example: origLine="it's becouse of them", StartCol=5, Length=7, NewString="because" +func (Fixer) Finish() {} - curOrigLinePos := 0 - for i := range lineIssues { - fix := lineIssues[i].Replacement.Inline - if fix.StartCol < curOrigLinePos { - p.log.Warnf("Line %d has multiple intersecting issues: %#v", lineNum, lineIssues) - return nil - } +func (p Fixer) printStat() { + p.sw.PrintStages() +} - if curOrigLinePos != fix.StartCol { - newLineBuf.Write(origLine[curOrigLinePos:fix.StartCol]) +func skipTextEditWithoutPosition(issue *result.Issue) bool { + var onlyMessage int + var count int + for _, sf := range issue.SuggestedFixes { + for _, edit := range sf.TextEdits { + count++ + if edit.Pos == token.NoPos && edit.End == token.NoPos { + onlyMessage++ + } } - newLineBuf.WriteString(fix.NewString) - curOrigLinePos = fix.StartCol + fix.Length - } - if curOrigLinePos != len(origLine) { - newLineBuf.Write(origLine[curOrigLinePos:]) } - mergedIssue := lineIssues[0] // use text from the first issue (it's not really used) - mergedIssue.Replacement = &result.Replacement{ - NewLines: []string{newLineBuf.String()}, - } - return &mergedIssue + return count == onlyMessage } -func (p Fixer) findNotIntersectingIssues(issues []result.Issue) []result.Issue { - sort.SliceStable(issues, func(i, j int) bool { - a, b := issues[i], issues[j] - return a.Line() < b.Line() - }) +// validateEdits returns a list of edits that is sorted and +// contains no duplicate edits. Returns the index of some +// overlapping adjacent edits if there is one and <0 if the +// edits are valid. +// +//nolint:gocritic // Copy of go/analysis/internal/checker/checker.go +func validateEdits(edits []diff.Edit) ([]diff.Edit, int) { + if len(edits) == 0 { + return nil, -1 + } - var ret []result.Issue - var currentEnd int - for i := range issues { - issue := &issues[i] - rng := issue.GetLineRange() - if rng.From <= currentEnd { - p.log.Infof("Skip issue %#v: intersects with end %d", issue, currentEnd) - continue // skip intersecting issue - } - p.log.Infof("Fix issue %#v with range %v", issue, issue.GetLineRange()) - ret = append(ret, *issue) - currentEnd = rng.To + equivalent := func(x, y diff.Edit) bool { + return x.Start == y.Start && x.End == y.End && x.New == y.New } - return ret -} + diff.SortEdits(edits) -func (p Fixer) writeFixedFile(origFileLines [][]byte, issues []result.Issue, tmpOutFile *os.File) error { - // issues aren't intersecting + unique := []diff.Edit{edits[0]} - nextIssueIndex := 0 - for i := 0; i < len(origFileLines); i++ { - var outLine string - var nextIssue *result.Issue - if nextIssueIndex != len(issues) { - nextIssue = &issues[nextIssueIndex] - } + invalid := -1 - origFileLineNumber := i + 1 - if nextIssue == nil || origFileLineNumber != nextIssue.GetLineRange().From { - outLine = string(origFileLines[i]) - } else { - nextIssueIndex++ - rng := nextIssue.GetLineRange() - if rng.From > rng.To { - // Maybe better decision is to skip such issues, re-evaluate if regressed. - p.log.Warnf("[fixer]: issue line range is probably invalid, fix can be incorrect (from=%d, to=%d, linter=%s)", - rng.From, rng.To, nextIssue.FromLinter, - ) - } - i += rng.To - rng.From - if nextIssue.Replacement.NeedOnlyDelete { - continue + for i := 1; i < len(edits); i++ { + prev, cur := edits[i-1], edits[i] + // We skip over equivalent edits without considering them + // an error. This handles identical edits coming from the + // multiple ways of loading a package into a + // *go/packages.Packages for testing, e.g. packages "p" and "p [p.test]". + if !equivalent(prev, cur) { + unique = append(unique, cur) + if prev.End > cur.Start { + invalid = i } - outLine = strings.Join(nextIssue.Replacement.NewLines, "\n") } + } + return unique, invalid +} - if i < len(origFileLines)-1 { - outLine += "\n" - } - if _, err := tmpOutFile.WriteString(outLine); err != nil { - return fmt.Errorf("failed to write output line: %w", err) - } +// diff3Conflict returns an error describing two conflicting sets of +// edits on a file at path. +// Copy of go/analysis/internal/checker/checker.go +func diff3Conflict(path, xlabel, ylabel string, xedits, yedits []diff.Edit) error { + contents, err := os.ReadFile(path) + if err != nil { + return err } + oldlabel, old := "base", string(contents) - return nil -} + xdiff, err := diff.ToUnified(oldlabel, xlabel, old, xedits, diff.DefaultContextLines) + if err != nil { + return err + } + ydiff, err := diff.ToUnified(oldlabel, ylabel, old, yedits, diff.DefaultContextLines) + if err != nil { + return err + } -func (p Fixer) printStat() { - p.sw.PrintStages() + return fmt.Errorf("conflicting edits from %s and %s on %s\nfirst edits:\n%s\nsecond edits:\n%s", + xlabel, ylabel, path, xdiff, ydiff) } diff --git a/pkg/result/processors/max_from_linter.go b/pkg/result/processors/max_from_linter.go index 0680c3f296c7..690cdf3f8ab8 100644 --- a/pkg/result/processors/max_from_linter.go +++ b/pkg/result/processors/max_from_linter.go @@ -34,7 +34,7 @@ func (p *MaxFromLinter) Process(issues []result.Issue) ([]result.Issue, error) { } return filterIssuesUnsafe(issues, func(issue *result.Issue) bool { - if issue.Replacement != nil && p.cfg.Issues.NeedFix { + if issue.SuggestedFixes != nil && p.cfg.Issues.NeedFix { // we need to fix all issues at once => we need to return all of them return true } diff --git a/pkg/result/processors/max_same_issues.go b/pkg/result/processors/max_same_issues.go index 1647cace09b9..0d1c28628251 100644 --- a/pkg/result/processors/max_same_issues.go +++ b/pkg/result/processors/max_same_issues.go @@ -36,7 +36,7 @@ func (p *MaxSameIssues) Process(issues []result.Issue) ([]result.Issue, error) { } return filterIssuesUnsafe(issues, func(issue *result.Issue) bool { - if issue.Replacement != nil && p.cfg.Issues.NeedFix { + if issue.SuggestedFixes != nil && p.cfg.Issues.NeedFix { // we need to fix all issues at once => we need to return all of them return true } diff --git a/pkg/result/processors/uniq_by_line.go b/pkg/result/processors/uniq_by_line.go index 115196d9a742..c3a59ac000c6 100644 --- a/pkg/result/processors/uniq_by_line.go +++ b/pkg/result/processors/uniq_by_line.go @@ -36,7 +36,7 @@ func (p *UniqByLine) Process(issues []result.Issue) ([]result.Issue, error) { func (*UniqByLine) Finish() {} func (p *UniqByLine) shouldPassIssue(issue *result.Issue) bool { - if issue.Replacement != nil && p.cfg.Issues.NeedFix { + if issue.SuggestedFixes != nil && p.cfg.Issues.NeedFix { // if issue will be auto-fixed we shouldn't collapse issues: // e.g. one line can contain 2 misspellings, they will be in 2 issues and misspell should fix both of them. return true diff --git a/test/output_test.go b/test/output_test.go index 52352ca693ce..026395e5d2e4 100644 --- a/test/output_test.go +++ b/test/output_test.go @@ -13,7 +13,7 @@ import ( ) //nolint:misspell // misspelling is intentional -const expectedJSONOutput = `{"Issues":[{"FromLinter":"misspell","Text":"` + "`" + `occured` + "`" + ` is a misspelling of ` + "`" + `occurred` + "`" + `","Severity":"","SourceLines":["\t// comment with incorrect spelling: occured // want \"` + "`" + `occured` + "`" + ` is a misspelling of ` + "`" + `occurred` + "`" + `\""],"Replacement":{"NeedOnlyDelete":false,"NewLines":null,"Inline":{"StartCol":37,"Length":7,"NewString":"occurred"}},"Pos":{"Filename":"testdata/output.go","Offset":0,"Line":6,"Column":38},"ExpectNoLint":false,"ExpectedNoLintLinter":""}]` +const expectedJSONOutput = `{"Issues":[{"FromLinter":"misspell","Text":"` + "`" + `occured` + "`" + ` is a misspelling of ` + "`" + `occurred` + "`" + `","Severity":"","SourceLines":["\t// comment with incorrect spelling: occured // want \"` + "`" + `occured` + "`" + ` is a misspelling of ` + "`" + `occurred` + "`" + `\""],"Pos":{"Filename":"testdata/output.go","Offset":159,"Line":6,"Column":38},"SuggestedFixes":[{"Message":"","TextEdits":[{"Pos":159,"End":166,"NewText":"b2NjdXJyZWQ="}]}],"ExpectNoLint":false,"ExpectedNoLintLinter":""}]` func TestOutput_lineNumber(t *testing.T) { sourcePath := filepath.Join(testdataDir, "output.go") From 632b805e7885749350930cdcdb8875344c0cb6a3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:30:52 +0100 Subject: [PATCH 16/57] chore: migrate nolintlint --- .../nolintlint/internal/nolintlint.go | 72 +++++++++---------- .../nolintlint/internal/nolintlint_test.go | 69 +++++++++--------- .../nolintlint/testdata/fix/in/nolintlint.go | 1 - .../nolintlint/testdata/fix/out/nolintlint.go | 5 +- 4 files changed, 73 insertions(+), 74 deletions(-) diff --git a/pkg/golinters/nolintlint/internal/nolintlint.go b/pkg/golinters/nolintlint/internal/nolintlint.go index 610682a9baa4..b21bc2b52463 100644 --- a/pkg/golinters/nolintlint/internal/nolintlint.go +++ b/pkg/golinters/nolintlint/internal/nolintlint.go @@ -2,6 +2,7 @@ package internal import ( + "go/token" "regexp" "strings" @@ -23,6 +24,8 @@ const ( type Needs uint +const commentMark = "//" + var commentPattern = regexp.MustCompile(`^//\s*(nolint)(:\s*[\w-]+\s*(?:,\s*[\w-]+\s*)*)?\b`) // matches a complete nolint directive @@ -70,12 +73,12 @@ func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { leadingSpace = leadingSpaceMatches[1] } - directiveWithOptionalLeadingSpace := "//" + directiveWithOptionalLeadingSpace := commentMark if leadingSpace != "" { directiveWithOptionalLeadingSpace += " " } - split := strings.Split(strings.SplitN(comment.Text, ":", 2)[0], "//") + split := strings.Split(strings.SplitN(comment.Text, ":", 2)[0], commentMark) directiveWithOptionalLeadingSpace += strings.TrimSpace(split[1]) pos := pass.Fset.Position(comment.Pos()) @@ -83,32 +86,31 @@ func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { // check for, report and eliminate leading spaces, so we can check for other issues if leadingSpace != "" { - removeWhitespace := &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: pos.Column + 1, - Length: len(leadingSpace), - }, - } + removeWhitespace := []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: token.Pos(pos.Offset), + End: token.Pos(pos.Offset + len(commentMark) + len(leadingSpace)), + NewText: []byte(commentMark), + }}, + }} if (l.needs & NeedsMachineOnly) != 0 { issue := &result.Issue{ - FromLinter: LinterName, - Text: formatNotMachine(comment.Text), - Pos: pos, - Replacement: removeWhitespace, + FromLinter: LinterName, + Text: formatNotMachine(comment.Text), + Pos: pos, + SuggestedFixes: removeWhitespace, } issues = append(issues, goanalysis.NewIssue(issue, pass)) } else if len(leadingSpace) > 1 { issue := &result.Issue{ - FromLinter: LinterName, - Text: formatExtraLeadingSpace(comment.Text), - Pos: pos, - Replacement: removeWhitespace, + FromLinter: LinterName, + Text: formatExtraLeadingSpace(comment.Text), + Pos: pos, + SuggestedFixes: removeWhitespace, } - issue.Replacement.Inline.NewString = " " // assume a single space was intended - issues = append(issues, goanalysis.NewIssue(issue, pass)) } } @@ -160,27 +162,21 @@ func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { // when detecting unused directives, we send all the directives through and filter them out in the nolint processor if (l.needs & NeedsUnused) != 0 { - removeNolintCompletely := &result.Replacement{} - - startCol := pos.Column - 1 - - if startCol == 0 { - // if the directive starts from a new line, remove the line - removeNolintCompletely.NeedOnlyDelete = true - } else { - removeNolintCompletely.Inline = &result.InlineFix{ - StartCol: startCol, - Length: end.Column - pos.Column, - } - } + removeNolintCompletely := []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: token.Pos(pos.Offset), + End: token.Pos(end.Offset), + NewText: nil, + }}, + }} if len(linters) == 0 { issue := &result.Issue{ - FromLinter: LinterName, - Text: formatUnusedCandidate(comment.Text, ""), - Pos: pos, - ExpectNoLint: true, - Replacement: removeNolintCompletely, + FromLinter: LinterName, + Text: formatUnusedCandidate(comment.Text, ""), + Pos: pos, + ExpectNoLint: true, + SuggestedFixes: removeNolintCompletely, } issues = append(issues, goanalysis.NewIssue(issue, pass)) @@ -194,11 +190,11 @@ func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { ExpectedNoLintLinter: linter, } - // only offer replacement if there is a single linter + // only offer SuggestedFix if there is a single linter // because of issues around commas and the possibility of all // linters being removed if len(linters) == 1 { - issue.Replacement = removeNolintCompletely + issue.SuggestedFixes = removeNolintCompletely } issues = append(issues, goanalysis.NewIssue(issue, pass)) diff --git a/pkg/golinters/nolintlint/internal/nolintlint_test.go b/pkg/golinters/nolintlint/internal/nolintlint_test.go index 05a8f7bb7a22..0780882bdbba 100644 --- a/pkg/golinters/nolintlint/internal/nolintlint_test.go +++ b/pkg/golinters/nolintlint/internal/nolintlint_test.go @@ -124,24 +124,26 @@ func foo() { { FromLinter: "nolintlint", Text: "directive `// nolint` should be written without leading space as `//nolint`", - Replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 10, - Length: 1, - }, - }, - Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: 34, + End: 37, + NewText: []byte(commentMark), + }}, + }}, }, { FromLinter: "nolintlint", Text: "directive `// nolint` should be written without leading space as `//nolint`", - Replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 10, - Length: 3, - }, - }, - Pos: token.Position{Filename: "testing.go", Offset: 52, Line: 5, Column: 9}, + Pos: token.Position{Filename: "testing.go", Offset: 52, Line: 5, Column: 9}, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: 52, + End: 57, + NewText: []byte(commentMark), + }}, + }}, }, }, }, @@ -184,13 +186,13 @@ func foo() { expected: []result.Issue{{ FromLinter: "nolintlint", Text: "directive `//nolint` is unused", - Replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 8, - Length: 8, - }, - }, - Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: 34, + End: 42, + }}, + }}, ExpectNoLint: true, }}, }, @@ -206,13 +208,13 @@ func foo() { expected: []result.Issue{{ FromLinter: "nolintlint", Text: "directive `//nolint:somelinter` is unused for linter \"somelinter\"", - Replacement: &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: 8, - Length: 19, - }, - }, - Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + Pos: token.Position{Filename: "testing.go", Offset: 34, Line: 4, Column: 9}, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: 34, + End: 53, + }}, + }}, ExpectNoLint: true, ExpectedNoLintLinter: "somelinter", }}, @@ -230,10 +232,13 @@ func foo() { expected: []result.Issue{{ FromLinter: "nolintlint", Text: "directive `//nolint:somelinter` is unused for linter \"somelinter\"", - Replacement: &result.Replacement{ - NeedOnlyDelete: true, - }, - Pos: token.Position{Filename: "testing.go", Offset: 13, Line: 3, Column: 1}, + Pos: token.Position{Filename: "testing.go", Offset: 13, Line: 3, Column: 1}, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: 13, + End: 32, + }}, + }}, ExpectNoLint: true, ExpectedNoLintLinter: "somelinter", }}, diff --git a/pkg/golinters/nolintlint/testdata/fix/in/nolintlint.go b/pkg/golinters/nolintlint/testdata/fix/in/nolintlint.go index bfb0e91eef93..231084458b9e 100644 --- a/pkg/golinters/nolintlint/testdata/fix/in/nolintlint.go +++ b/pkg/golinters/nolintlint/testdata/fix/in/nolintlint.go @@ -8,7 +8,6 @@ func nolintlint() { fmt.Println() // nolint:bob // leading space should be dropped fmt.Println() // nolint:bob // leading spaces should be dropped - // note that the next lines will retain trailing whitespace when fixed fmt.Println() //nolint:all // nolint should be dropped fmt.Println() //nolint:lll // nolint should be dropped diff --git a/pkg/golinters/nolintlint/testdata/fix/out/nolintlint.go b/pkg/golinters/nolintlint/testdata/fix/out/nolintlint.go index 93b34c6ed955..d31b16705420 100644 --- a/pkg/golinters/nolintlint/testdata/fix/out/nolintlint.go +++ b/pkg/golinters/nolintlint/testdata/fix/out/nolintlint.go @@ -8,9 +8,8 @@ func nolintlint() { fmt.Println() //nolint:bob // leading space should be dropped fmt.Println() //nolint:bob // leading spaces should be dropped - // note that the next lines will retain trailing whitespace when fixed - fmt.Println() - fmt.Println() + fmt.Println() + fmt.Println() fmt.Println() //nolint:alice,lll // we don't drop individual linters from lists } From 87f42b53477b018381ed0c97342265ded915fbf7 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:31:23 +0100 Subject: [PATCH 17/57] chore: migrate gofmt --- pkg/golinters/gofmt/gofmt.go | 38 +++++++++--------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/pkg/golinters/gofmt/gofmt.go b/pkg/golinters/gofmt/gofmt.go index 289ceab8aec6..24e827f6875c 100644 --- a/pkg/golinters/gofmt/gofmt.go +++ b/pkg/golinters/gofmt/gofmt.go @@ -2,7 +2,6 @@ package gofmt import ( "fmt" - "sync" gofmtAPI "github.com/golangci/gofmt/gofmt" "golang.org/x/tools/go/analysis" @@ -16,9 +15,6 @@ import ( const linterName = "gofmt" func New(settings *config.GoFmtSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, @@ -33,56 +29,40 @@ func New(settings *config.GoFmtSettings) *goanalysis.Linter { nil, ).WithContextSetter(func(lintCtx *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (any, error) { - issues, err := runGofmt(lintCtx, pass, settings) + err := runGofmt(lintCtx, pass, settings) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runGofmt(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoFmtSettings) ([]goanalysis.Issue, error) { - fileNames := internal.GetFileNames(pass) - +func runGofmt(lintCtx *linter.Context, pass *analysis.Pass, settings *config.GoFmtSettings) error { var rewriteRules []gofmtAPI.RewriteRule for _, rule := range settings.RewriteRules { rewriteRules = append(rewriteRules, gofmtAPI.RewriteRule(rule)) } - var issues []goanalysis.Issue + for _, file := range pass.Files { + position := goanalysis.GetFilePosition(pass, file) - for _, f := range fileNames { - diff, err := gofmtAPI.RunRewrite(f, settings.Simplify, rewriteRules) + diff, err := gofmtAPI.RunRewrite(position.Filename, settings.Simplify, rewriteRules) if err != nil { // TODO: skip - return nil, err + return err } if diff == nil { continue } - is, err := internal.ExtractIssuesFromPatch(string(diff), lintCtx, linterName, getIssuedTextGoFmt) + err = internal.ExtractDiagnosticFromPatch(pass, file, string(diff), lintCtx, getIssuedTextGoFmt) if err != nil { - return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %w", string(diff), err) - } - - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) + return fmt.Errorf("can't extract issues from gofmt diff output %q: %w", string(diff), err) } } - return issues, nil + return nil } func getIssuedTextGoFmt(settings *config.LintersSettings) string { From acfdeccd6538a2954a74eaf9a149d0e0164dcebf Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:31:34 +0100 Subject: [PATCH 18/57] chore: migrate gofumpt --- pkg/golinters/gofumpt/gofumpt.go | 45 +++++++++----------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/pkg/golinters/gofumpt/gofumpt.go b/pkg/golinters/gofumpt/gofumpt.go index 3bb7df12e2b4..93b77298970a 100644 --- a/pkg/golinters/gofumpt/gofumpt.go +++ b/pkg/golinters/gofumpt/gofumpt.go @@ -6,7 +6,6 @@ import ( "io" "os" "strings" - "sync" "github.com/shazow/go-diff/difflib" "golang.org/x/tools/go/analysis" @@ -25,9 +24,6 @@ type differ interface { } func New(settings *config.GofumptSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - diff := difflib.New() var options format.Options @@ -53,63 +49,48 @@ func New(settings *config.GofumptSettings) *goanalysis.Linter { nil, ).WithContextSetter(func(lintCtx *linter.Context) { analyzer.Run = func(pass *analysis.Pass) (any, error) { - issues, err := runGofumpt(lintCtx, pass, diff, options) + err := runGofumpt(lintCtx, pass, diff, options) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runGofumpt(lintCtx *linter.Context, pass *analysis.Pass, diff differ, options format.Options) ([]goanalysis.Issue, error) { - fileNames := internal.GetFileNames(pass) +func runGofumpt(lintCtx *linter.Context, pass *analysis.Pass, diff differ, options format.Options) error { + for _, file := range pass.Files { + position := goanalysis.GetFilePosition(pass, file) - var issues []goanalysis.Issue - - for _, f := range fileNames { - input, err := os.ReadFile(f) + input, err := os.ReadFile(position.Filename) if err != nil { - return nil, fmt.Errorf("unable to open file %s: %w", f, err) + return fmt.Errorf("unable to open file %s: %w", position.Filename, err) } output, err := format.Source(input, options) if err != nil { - return nil, fmt.Errorf("error while running gofumpt: %w", err) + return fmt.Errorf("error while running gofumpt: %w", err) } if !bytes.Equal(input, output) { - out := bytes.NewBufferString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", f)) + out := bytes.NewBufferString(fmt.Sprintf("--- %[1]s\n+++ %[1]s\n", position.Filename)) err := diff.Diff(out, bytes.NewReader(input), bytes.NewReader(output)) if err != nil { - return nil, fmt.Errorf("error while running gofumpt: %w", err) + return fmt.Errorf("error while running gofumpt: %w", err) } diff := out.String() - is, err := internal.ExtractIssuesFromPatch(diff, lintCtx, linterName, getIssuedTextGoFumpt) - if err != nil { - return nil, fmt.Errorf("can't extract issues from gofumpt diff output %q: %w", diff, err) - } - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) + err = internal.ExtractDiagnosticFromPatch(pass, file, diff, lintCtx, getIssuedTextGoFumpt) + if err != nil { + return fmt.Errorf("can't extract issues from gofumpt diff output %q: %w", diff, err) } } } - return issues, nil + return nil } func getLangVersion(settings *config.GofumptSettings) string { From 473ab4e5a44a88084a1f9caf45fef9c89518840c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:31:56 +0100 Subject: [PATCH 19/57] chore: migrate goimports --- pkg/golinters/goimports/goimports.go | 38 +++++-------------- .../goimports/testdata/fix/in/goimports.go | 2 +- .../goimports/testdata/fix/out/goimports.go | 2 +- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/pkg/golinters/goimports/goimports.go b/pkg/golinters/goimports/goimports.go index de965d5c8543..54029d49fc26 100644 --- a/pkg/golinters/goimports/goimports.go +++ b/pkg/golinters/goimports/goimports.go @@ -2,7 +2,6 @@ package goimports import ( "fmt" - "sync" goimportsAPI "github.com/golangci/gofmt/goimports" "golang.org/x/tools/go/analysis" @@ -17,9 +16,6 @@ import ( const linterName = "goimports" func New(settings *config.GoImportsSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, @@ -36,51 +32,35 @@ func New(settings *config.GoImportsSettings) *goanalysis.Linter { imports.LocalPrefix = settings.LocalPrefixes analyzer.Run = func(pass *analysis.Pass) (any, error) { - issues, err := runGoImports(lintCtx, pass) + err := runGoImports(lintCtx, pass) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runGoImports(lintCtx *linter.Context, pass *analysis.Pass) ([]goanalysis.Issue, error) { - fileNames := internal.GetFileNames(pass) +func runGoImports(lintCtx *linter.Context, pass *analysis.Pass) error { + for _, file := range pass.Files { + position := goanalysis.GetFilePosition(pass, file) - var issues []goanalysis.Issue - - for _, f := range fileNames { - diff, err := goimportsAPI.Run(f) + diff, err := goimportsAPI.Run(position.Filename) if err != nil { // TODO: skip - return nil, err + return err } if diff == nil { continue } - is, err := internal.ExtractIssuesFromPatch(string(diff), lintCtx, linterName, getIssuedTextGoImports) + err = internal.ExtractDiagnosticFromPatch(pass, file, string(diff), lintCtx, getIssuedTextGoImports) if err != nil { - return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %w", string(diff), err) - } - - for i := range is { - issues = append(issues, goanalysis.NewIssue(&is[i], pass)) + return fmt.Errorf("can't extract issues from gofmt diff output %q: %w", string(diff), err) } } - return issues, nil + return nil } func getIssuedTextGoImports(settings *config.LintersSettings) string { diff --git a/pkg/golinters/goimports/testdata/fix/in/goimports.go b/pkg/golinters/goimports/testdata/fix/in/goimports.go index 1808bf18dde4..456583c75c87 100644 --- a/pkg/golinters/goimports/testdata/fix/in/goimports.go +++ b/pkg/golinters/goimports/testdata/fix/in/goimports.go @@ -1,4 +1,4 @@ -//golangcitest:args -Egofmt,goimports +//golangcitest:args -Egoimports //golangcitest:expected_exitcode 0 package p diff --git a/pkg/golinters/goimports/testdata/fix/out/goimports.go b/pkg/golinters/goimports/testdata/fix/out/goimports.go index 8e28c8ee3ef6..bbde3564817b 100644 --- a/pkg/golinters/goimports/testdata/fix/out/goimports.go +++ b/pkg/golinters/goimports/testdata/fix/out/goimports.go @@ -1,4 +1,4 @@ -//golangcitest:args -Egofmt,goimports +//golangcitest:args -Egoimports //golangcitest:expected_exitcode 0 package p From a85e53bfda8f3d13a4fe0f253125462746096c8e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:32:10 +0100 Subject: [PATCH 20/57] chore: migrate misspell --- pkg/golinters/misspell/misspell.go | 81 +++++++++++------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/pkg/golinters/misspell/misspell.go b/pkg/golinters/misspell/misspell.go index 44409cec9dee..0f13e61038e0 100644 --- a/pkg/golinters/misspell/misspell.go +++ b/pkg/golinters/misspell/misspell.go @@ -2,9 +2,9 @@ package misspell import ( "fmt" + "go/ast" "go/token" "strings" - "sync" "unicode" "github.com/golangci/misspell" @@ -12,17 +12,12 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/golinters/internal" "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "misspell" func New(settings *config.MisspellSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, @@ -42,42 +37,25 @@ func New(settings *config.MisspellSettings) *goanalysis.Linter { return nil, ruleErr } - issues, err := runMisspell(lintCtx, pass, replacer, settings.Mode) + err := runMisspell(lintCtx, pass, replacer, settings.Mode) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues }).WithLoadMode(goanalysis.LoadModeSyntax) } -func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer, mode string) ([]goanalysis.Issue, error) { - fileNames := internal.GetFileNames(pass) - - var issues []goanalysis.Issue - for _, filename := range fileNames { - lintIssues, err := runMisspellOnFile(lintCtx, filename, replacer, mode) +func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer, mode string) error { + for _, file := range pass.Files { + err := runMisspellOnFile(lintCtx, pass, file, replacer, mode) if err != nil { - return nil, err - } - - for i := range lintIssues { - issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) + return err } } - return issues, nil + return nil } func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replacer, error) { @@ -112,10 +90,12 @@ func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replac return replacer, nil } -func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *misspell.Replacer, mode string) ([]result.Issue, error) { +func runMisspellOnFile(lintCtx *linter.Context, pass *analysis.Pass, file *ast.File, replacer *misspell.Replacer, mode string) error { + filename := goanalysis.GetFilePosition(pass, file).Filename + fileContent, err := lintCtx.FileCache.GetFileBytes(filename) if err != nil { - return nil, fmt.Errorf("can't get file %s contents: %w", filename, err) + return fmt.Errorf("can't get file %s contents: %w", filename, err) } // `r.ReplaceGo` doesn't find issues inside strings: it searches only inside comments. @@ -129,36 +109,31 @@ func runMisspellOnFile(lintCtx *linter.Context, filename string, replacer *missp replace = replacer.Replace } - _, diffs := replace(string(fileContent)) + f := pass.Fset.File(file.Pos()) - var res []result.Issue + _, diffs := replace(string(fileContent)) for _, diff := range diffs { text := fmt.Sprintf("`%s` is a misspelling of `%s`", diff.Original, diff.Corrected) - pos := token.Position{ - Filename: filename, - Line: diff.Line, - Column: diff.Column + 1, - } - - replacement := &result.Replacement{ - Inline: &result.InlineFix{ - StartCol: diff.Column, - Length: len(diff.Original), - NewString: diff.Corrected, - }, - } - - res = append(res, result.Issue{ - Pos: pos, - Text: text, - FromLinter: linterName, - Replacement: replacement, + start := f.LineStart(diff.Line) + token.Pos(diff.Column) + end := f.LineStart(diff.Line) + token.Pos(diff.Column+len(diff.Original)) + + pass.Report(analysis.Diagnostic{ + Pos: start, + End: end, + Message: text, + SuggestedFixes: []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: start, + End: end, + NewText: []byte(diff.Corrected), + }}, + }}, }) } - return res, nil + return nil } func appendExtraWords(replacer *misspell.Replacer, extraWords []config.MisspellExtraWords) error { From 75e4944ddb9dfdfe2b9516ced2ab4f6e3e92f76a Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:32:43 +0100 Subject: [PATCH 21/57] feat: support autofix for revive --- pkg/golinters/revive/revive.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pkg/golinters/revive/revive.go b/pkg/golinters/revive/revive.go index 77d496452086..a3f7daa894ee 100644 --- a/pkg/golinters/revive/revive.go +++ b/pkg/golinters/revive/revive.go @@ -165,7 +165,7 @@ func toIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue { lineRangeTo = object.Position.Start.Line } - return goanalysis.NewIssue(&result.Issue{ + issue := &result.Issue{ Severity: string(object.Severity), Text: fmt.Sprintf("%s: %s", object.RuleName, object.Failure.Failure), Pos: token.Position{ @@ -179,7 +179,25 @@ func toIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue { To: lineRangeTo, }, FromLinter: linterName, - }, pass) + } + + if object.ReplacementLine != "" { + f := pass.Fset.File(token.Pos(object.Position.Start.Offset)) + + start := f.LineStart(object.Position.Start.Line) + + end := goanalysis.EndOfLinePos(f, object.Position.End.Line) + + issue.SuggestedFixes = []analysis.SuggestedFix{{ + TextEdits: []analysis.TextEdit{{ + Pos: start, + End: end, + NewText: []byte(object.ReplacementLine), + }}, + }} + } + + return goanalysis.NewIssue(issue, pass) } // This function mimics the GetConfig function of revive. From 7b016e6e0b2d35befb8164c76bbd4efc60e2bff0 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:33:35 +0100 Subject: [PATCH 22/57] chore: tag all linters with autofix --- pkg/lint/lintersdb/builder_linter.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 4c30e0d2afe9..6a09f5862f3c 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -161,6 +161,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.58.0"). WithPresets(linter.PresetStyle). WithLoadForGoAnalysis(). + WithAutoFix(). WithURL("https://github.com/lasiar/canonicalheader"), linter.NewConfig(containedctx.New()). @@ -215,6 +216,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(dupword.New(&cfg.LintersSettings.DupWord)). WithSince("v1.50.0"). WithPresets(linter.PresetComment). + WithAutoFix(). WithURL("https://github.com/Abirdcfly/dupword"), linter.NewConfig(durationcheck.New()). @@ -246,6 +248,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.32.0"). WithPresets(linter.PresetBugs, linter.PresetError). WithLoadForGoAnalysis(). + WithAutoFix(). WithURL("https://github.com/polyfloyd/go-errorlint"), linter.NewConfig(linter.NewNoopDeprecated("execinquery", cfg, linter.DeprecationError)). @@ -298,6 +301,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.58.0"). WithPresets(linter.PresetPerformance). WithLoadForGoAnalysis(). + WithAutoFix(). WithURL("https://github.com/Crocmagnon/fatcontext"), linter.NewConfig(funlen.New(&cfg.LintersSettings.Funlen)). @@ -315,6 +319,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.51.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/nunnatsa/ginkgolinter"), linter.NewConfig(gocheckcompilerdirectives.New()). @@ -376,6 +381,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithPresets(linter.PresetStyle, linter.PresetError). WithLoadForGoAnalysis(). WithAlternativeNames("goerr113"). + WithAutoFix(). WithURL("https://github.com/Djarvur/go-err113"), linter.NewConfig(gofmt.New(&cfg.LintersSettings.Gofmt)). @@ -447,6 +453,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle). WithAlternativeNames(megacheckName). + WithAutoFix(). WithURL("https://github.com/dominikh/go-tools/tree/master/simple"), linter.NewConfig(gosmopolitan.New(&cfg.LintersSettings.Gosmopolitan)). @@ -460,6 +467,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.0.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetBugs, linter.PresetMetaLinter). + WithAutoFix(). WithAlternativeNames("vet", "vetshadow"). WithURL("https://pkg.go.dev/cmd/vet"), @@ -478,12 +486,14 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.62.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/uudashr/iface"), linter.NewConfig(importas.New(&cfg.LintersSettings.ImportAs)). WithSince("v1.38.0"). WithPresets(linter.PresetStyle). WithLoadForGoAnalysis(). + WithAutoFix(). WithURL("https://github.com/julz/importas"), linter.NewConfig(inamedparam.New(&cfg.LintersSettings.Inamedparam)). @@ -512,6 +522,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.57.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/ckaznocha/intrange"). WithNoopFallback(cfg, linter.IsGoLowerThanGo122()), @@ -571,6 +582,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(nakedret.New(&cfg.LintersSettings.Nakedret)). WithSince("v1.19.0"). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/alexkohler/nakedret"), linter.NewConfig(nestif.New(&cfg.LintersSettings.Nestif)). @@ -593,6 +605,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(nlreturn.New(&cfg.LintersSettings.Nlreturn)). WithSince("v1.30.0"). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/ssgreg/nlreturn"), linter.NewConfig(noctx.New()). @@ -628,6 +641,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.55.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetPerformance). + WithAutoFix(). WithURL("https://github.com/catenacyber/perfsprint"), linter.NewConfig(prealloc.New(&cfg.LintersSettings.Prealloc)). @@ -668,6 +682,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.37.0"). WithPresets(linter.PresetStyle, linter.PresetMetaLinter). ConsiderSlow(). + WithAutoFix(). WithURL("https://github.com/mgechev/revive"), linter.NewConfig(rowserrcheck.New(&cfg.LintersSettings.RowsErrCheck)). @@ -706,6 +721,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithLoadForGoAnalysis(). WithPresets(linter.PresetBugs, linter.PresetMetaLinter). WithAlternativeNames(megacheckName). + WithAutoFix(). WithURL("https://staticcheck.dev/"), linter.NewConfig(linter.NewNoopDeprecated("structcheck", cfg, linter.DeprecationError)). @@ -718,6 +734,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.20.0"). WithLoadForGoAnalysis(). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/dominikh/go-tools/tree/master/stylecheck"), linter.NewConfig(tagalign.New(&cfg.LintersSettings.TagAlign)). @@ -747,6 +764,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.55.0"). WithPresets(linter.PresetTest, linter.PresetBugs). WithLoadForGoAnalysis(). + WithAutoFix(). WithURL("https://github.com/Antonboom/testifylint"), linter.NewConfig(testpackage.New(&cfg.LintersSettings.Testpackage)). @@ -837,6 +855,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(wsl.New(&cfg.LintersSettings.WSL)). WithSince("v1.20.0"). WithPresets(linter.PresetStyle). + WithAutoFix(). WithURL("https://github.com/bombsimon/wsl"), linter.NewConfig(zerologlint.New()). From 8444ec12ea59821bc9cbca110f779590c8f5bc5b Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:34:16 +0100 Subject: [PATCH 23/57] tests: autofix canonicalheader --- .../canonicalheader/canonicalheader_test.go | 8 +++++++ .../testdata/fix/in/canonicalheader.go | 22 +++++++++++++++++++ .../testdata/fix/out/canonicalheader.go | 22 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 pkg/golinters/canonicalheader/testdata/fix/in/canonicalheader.go create mode 100644 pkg/golinters/canonicalheader/testdata/fix/out/canonicalheader.go diff --git a/pkg/golinters/canonicalheader/canonicalheader_test.go b/pkg/golinters/canonicalheader/canonicalheader_test.go index cbe61a9b74dd..de9139a3a693 100644 --- a/pkg/golinters/canonicalheader/canonicalheader_test.go +++ b/pkg/golinters/canonicalheader/canonicalheader_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/canonicalheader/testdata/fix/in/canonicalheader.go b/pkg/golinters/canonicalheader/testdata/fix/in/canonicalheader.go new file mode 100644 index 000000000000..d23bd6123992 --- /dev/null +++ b/pkg/golinters/canonicalheader/testdata/fix/in/canonicalheader.go @@ -0,0 +1,22 @@ +//golangcitest:args -Ecanonicalheader +//golangcitest:expected_exitcode 0 +package testdata + +import "net/http" + +func canonicalheader() { + v := http.Header{} + + v.Get("Test-HEader") + v.Set("Test-HEader", "value") + v.Add("Test-HEader", "value") + v.Del("Test-HEader") + v.Values("Test-HEader") + + v.Values("Sec-WebSocket-Accept") + + v.Set("Test-Header", "value") + v.Add("Test-Header", "value") + v.Del("Test-Header") + v.Values("Test-Header") +} diff --git a/pkg/golinters/canonicalheader/testdata/fix/out/canonicalheader.go b/pkg/golinters/canonicalheader/testdata/fix/out/canonicalheader.go new file mode 100644 index 000000000000..95ceceac2a29 --- /dev/null +++ b/pkg/golinters/canonicalheader/testdata/fix/out/canonicalheader.go @@ -0,0 +1,22 @@ +//golangcitest:args -Ecanonicalheader +//golangcitest:expected_exitcode 0 +package testdata + +import "net/http" + +func canonicalheader() { + v := http.Header{} + + v.Get("Test-Header") + v.Set("Test-Header", "value") + v.Add("Test-Header", "value") + v.Del("Test-Header") + v.Values("Test-Header") + + v.Values("Sec-WebSocket-Accept") + + v.Set("Test-Header", "value") + v.Add("Test-Header", "value") + v.Del("Test-Header") + v.Values("Test-Header") +} From 410281cfad7a78fa73715b601319285d9fbeedf6 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:34:29 +0100 Subject: [PATCH 24/57] tests: autofix dupword --- .../dupword/dupword_integration_test.go | 8 ++++++++ pkg/golinters/dupword/testdata/fix/in/dupword.go | 16 ++++++++++++++++ .../dupword/testdata/fix/out/dupword.go | 16 ++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 pkg/golinters/dupword/testdata/fix/in/dupword.go create mode 100644 pkg/golinters/dupword/testdata/fix/out/dupword.go diff --git a/pkg/golinters/dupword/dupword_integration_test.go b/pkg/golinters/dupword/dupword_integration_test.go index 9a5c50606e16..89d84ba7e9fb 100644 --- a/pkg/golinters/dupword/dupword_integration_test.go +++ b/pkg/golinters/dupword/dupword_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/dupword/testdata/fix/in/dupword.go b/pkg/golinters/dupword/testdata/fix/in/dupword.go new file mode 100644 index 000000000000..38e2bd067fea --- /dev/null +++ b/pkg/golinters/dupword/testdata/fix/in/dupword.go @@ -0,0 +1,16 @@ +//golangcitest:args -Edupword +//golangcitest:expected_exitcode 0 +package testdata + +import "fmt" + +func duplicateWordInComments() { + // this line include duplicated word the the + fmt.Println("hello") +} + +func duplicateWordInStr() { + a := "this line include duplicate word and and" + b := "print the\n the line, print the the \n\t the line. and and" + fmt.Println(a, b) +} diff --git a/pkg/golinters/dupword/testdata/fix/out/dupword.go b/pkg/golinters/dupword/testdata/fix/out/dupword.go new file mode 100644 index 000000000000..deb5092c98a5 --- /dev/null +++ b/pkg/golinters/dupword/testdata/fix/out/dupword.go @@ -0,0 +1,16 @@ +//golangcitest:args -Edupword +//golangcitest:expected_exitcode 0 +package testdata + +import "fmt" + +func duplicateWordInComments() { + // this line include duplicated word the + fmt.Println("hello") +} + +func duplicateWordInStr() { + a := "this line include duplicate word and" + b := "print the\n line, print the line. and" + fmt.Println(a, b) +} From e5c03da1fa49ab0b4112692a02410722c60ccaac Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:34:46 +0100 Subject: [PATCH 25/57] tests: autofix fatcontext --- .../fatcontext/fatcontext_integration_test.go | 8 ++ .../fatcontext/testdata/fix/in/fatcontext.go | 75 +++++++++++++++++++ .../fatcontext/testdata/fix/out/fatcontext.go | 75 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 pkg/golinters/fatcontext/testdata/fix/in/fatcontext.go create mode 100644 pkg/golinters/fatcontext/testdata/fix/out/fatcontext.go diff --git a/pkg/golinters/fatcontext/fatcontext_integration_test.go b/pkg/golinters/fatcontext/fatcontext_integration_test.go index e036cde55e10..38028322cc3f 100644 --- a/pkg/golinters/fatcontext/fatcontext_integration_test.go +++ b/pkg/golinters/fatcontext/fatcontext_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/fatcontext/testdata/fix/in/fatcontext.go b/pkg/golinters/fatcontext/testdata/fix/in/fatcontext.go new file mode 100644 index 000000000000..0cc9c7c4711d --- /dev/null +++ b/pkg/golinters/fatcontext/testdata/fix/in/fatcontext.go @@ -0,0 +1,75 @@ +//golangcitest:args -Efatcontext +//golangcitest:expected_exitcode 0 +package testdata + +import "context" + +func example() { + ctx := context.Background() + + for i := 0; i < 10; i++ { + ctx := context.WithValue(ctx, "key", i) + ctx = context.WithValue(ctx, "other", "val") + } + + for i := 0; i < 10; i++ { + ctx = context.WithValue(ctx, "key", i) + ctx = context.WithValue(ctx, "other", "val") + } + + for item := range []string{"one", "two", "three"} { + ctx = wrapContext(ctx) + ctx := context.WithValue(ctx, "key", item) + ctx = wrapContext(ctx) + } + + for { + ctx = wrapContext(ctx) + break + } +} + +func wrapContext(ctx context.Context) context.Context { + return context.WithoutCancel(ctx) +} + +// storing contexts in a struct isn't recommended, but local copies of a non-pointer struct should act like local copies of a context. +func inStructs(ctx context.Context) { + for i := 0; i < 10; i++ { + c := struct{ Ctx context.Context }{ctx} + c.Ctx = context.WithValue(c.Ctx, "key", i) + c.Ctx = context.WithValue(c.Ctx, "other", "val") + } + + for i := 0; i < 10; i++ { + c := []struct{ Ctx context.Context }{{ctx}} + c[0].Ctx = context.WithValue(c[0].Ctx, "key", i) + c[0].Ctx = context.WithValue(c[0].Ctx, "other", "val") + } + + c := struct{ Ctx context.Context }{ctx} + for i := 0; i < 10; i++ { + c := c + c.Ctx = context.WithValue(c.Ctx, "key", i) + c.Ctx = context.WithValue(c.Ctx, "other", "val") + } + + pc := &struct{ Ctx context.Context }{ctx} + for i := 0; i < 10; i++ { + c := pc + c.Ctx = context.WithValue(c.Ctx, "key", i) + c.Ctx = context.WithValue(c.Ctx, "other", "val") + } + + r := []struct{ Ctx context.Context }{{ctx}} + for i := 0; i < 10; i++ { + r[0].Ctx = context.WithValue(r[0].Ctx, "key", i) + r[0].Ctx = context.WithValue(r[0].Ctx, "other", "val") + } + + rp := []*struct{ Ctx context.Context }{{ctx}} + for i := 0; i < 10; i++ { + rp[0].Ctx = context.WithValue(rp[0].Ctx, "key", i) + rp[0].Ctx = context.WithValue(rp[0].Ctx, "other", "val") + } +} diff --git a/pkg/golinters/fatcontext/testdata/fix/out/fatcontext.go b/pkg/golinters/fatcontext/testdata/fix/out/fatcontext.go new file mode 100644 index 000000000000..494b812cd78e --- /dev/null +++ b/pkg/golinters/fatcontext/testdata/fix/out/fatcontext.go @@ -0,0 +1,75 @@ +//golangcitest:args -Efatcontext +//golangcitest:expected_exitcode 0 +package testdata + +import "context" + +func example() { + ctx := context.Background() + + for i := 0; i < 10; i++ { + ctx := context.WithValue(ctx, "key", i) + ctx = context.WithValue(ctx, "other", "val") + } + + for i := 0; i < 10; i++ { + ctx := context.WithValue(ctx, "key", i) + ctx = context.WithValue(ctx, "other", "val") + } + + for item := range []string{"one", "two", "three"} { + ctx := wrapContext(ctx) + ctx := context.WithValue(ctx, "key", item) + ctx = wrapContext(ctx) + } + + for { + ctx := wrapContext(ctx) + break + } +} + +func wrapContext(ctx context.Context) context.Context { + return context.WithoutCancel(ctx) +} + +// storing contexts in a struct isn't recommended, but local copies of a non-pointer struct should act like local copies of a context. +func inStructs(ctx context.Context) { + for i := 0; i < 10; i++ { + c := struct{ Ctx context.Context }{ctx} + c.Ctx = context.WithValue(c.Ctx, "key", i) + c.Ctx = context.WithValue(c.Ctx, "other", "val") + } + + for i := 0; i < 10; i++ { + c := []struct{ Ctx context.Context }{{ctx}} + c[0].Ctx = context.WithValue(c[0].Ctx, "key", i) + c[0].Ctx = context.WithValue(c[0].Ctx, "other", "val") + } + + c := struct{ Ctx context.Context }{ctx} + for i := 0; i < 10; i++ { + c := c + c.Ctx = context.WithValue(c.Ctx, "key", i) + c.Ctx = context.WithValue(c.Ctx, "other", "val") + } + + pc := &struct{ Ctx context.Context }{ctx} + for i := 0; i < 10; i++ { + c := pc + c.Ctx := context.WithValue(c.Ctx, "key", i) + c.Ctx = context.WithValue(c.Ctx, "other", "val") + } + + r := []struct{ Ctx context.Context }{{ctx}} + for i := 0; i < 10; i++ { + r[0].Ctx := context.WithValue(r[0].Ctx, "key", i) + r[0].Ctx = context.WithValue(r[0].Ctx, "other", "val") + } + + rp := []*struct{ Ctx context.Context }{{ctx}} + for i := 0; i < 10; i++ { + rp[0].Ctx := context.WithValue(rp[0].Ctx, "key", i) + rp[0].Ctx = context.WithValue(rp[0].Ctx, "other", "val") + } +} From 993ac5ec70568f76457126dc3f01c41c000c1af3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:35:13 +0100 Subject: [PATCH 26/57] tests: autofix err113 --- .../err113/err113_integration_test.go | 8 +++++++ .../err113/testdata/fix/in/err113.go | 22 +++++++++++++++++++ .../err113/testdata/fix/out/err113.go | 22 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 pkg/golinters/err113/testdata/fix/in/err113.go create mode 100644 pkg/golinters/err113/testdata/fix/out/err113.go diff --git a/pkg/golinters/err113/err113_integration_test.go b/pkg/golinters/err113/err113_integration_test.go index 70a66dd1d1fb..30dbd2cf3cca 100644 --- a/pkg/golinters/err113/err113_integration_test.go +++ b/pkg/golinters/err113/err113_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/err113/testdata/fix/in/err113.go b/pkg/golinters/err113/testdata/fix/in/err113.go new file mode 100644 index 000000000000..4b17f2a107a9 --- /dev/null +++ b/pkg/golinters/err113/testdata/fix/in/err113.go @@ -0,0 +1,22 @@ +//golangcitest:args -Eerr113 +//golangcitest:expected_exitcode 0 +package testdata + +import "os" + +func SimpleEqual(e1, e2 error) bool { + return e1 == e2 +} + +func SimpleNotEqual(e1, e2 error) bool { + return e1 != e2 +} + +func CheckGoerr13Import(e error) bool { + f, err := os.Create("f.txt") + if err != nil { + return err == e + } + f.Close() + return false +} diff --git a/pkg/golinters/err113/testdata/fix/out/err113.go b/pkg/golinters/err113/testdata/fix/out/err113.go new file mode 100644 index 000000000000..3e82bd20a857 --- /dev/null +++ b/pkg/golinters/err113/testdata/fix/out/err113.go @@ -0,0 +1,22 @@ +//golangcitest:args -Eerr113 +//golangcitest:expected_exitcode 0 +package testdata + +import "os" + +func SimpleEqual(e1, e2 error) bool { + return errors.Is(e1, e2) +} + +func SimpleNotEqual(e1, e2 error) bool { + return !errors.Is(e1, e2) +} + +func CheckGoerr13Import(e error) bool { + f, err := os.Create("f.txt") + if err != nil { + return errors.Is(err, e) + } + f.Close() + return false +} From ddced09389424e4953289043740333997aa60c1f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:35:26 +0100 Subject: [PATCH 27/57] tests: autofix errorlint --- .../errorlint/errorlint_integration_test.go | 8 ++ .../errorlint/testdata/fix/in/errorlint.go | 87 +++++++++++++++++++ .../errorlint/testdata/fix/out/errorlint.go | 87 +++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 pkg/golinters/errorlint/testdata/fix/in/errorlint.go create mode 100644 pkg/golinters/errorlint/testdata/fix/out/errorlint.go diff --git a/pkg/golinters/errorlint/errorlint_integration_test.go b/pkg/golinters/errorlint/errorlint_integration_test.go index b25d3914d425..9d358088cb0d 100644 --- a/pkg/golinters/errorlint/errorlint_integration_test.go +++ b/pkg/golinters/errorlint/errorlint_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/errorlint/testdata/fix/in/errorlint.go b/pkg/golinters/errorlint/testdata/fix/in/errorlint.go new file mode 100644 index 000000000000..2c7cd98b62d5 --- /dev/null +++ b/pkg/golinters/errorlint/testdata/fix/in/errorlint.go @@ -0,0 +1,87 @@ +//golangcitest:args -Eerrorlint +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "errors" + "fmt" +) + +func Good() error { + err := errors.New("oops") + return fmt.Errorf("error: %w", err) +} + +func NonWrappingVerb() error { + err := errors.New("oops") + return fmt.Errorf("error: %v", err) +} + +func NonWrappingTVerb() error { + err := errors.New("oops") + return fmt.Errorf("error: %T", err) +} + +func DoubleNonWrappingVerb() error { + err := errors.New("oops") + return fmt.Errorf("%v %v", err, err) +} + +func ErrorOneWrap() error { + err1 := errors.New("oops1") + err2 := errors.New("oops2") + err3 := errors.New("oops3") + return fmt.Errorf("%v, %w, %v", err1, err2, err3) +} + +func ValidNonWrappingTVerb() error { + err1 := errors.New("oops1") + err2 := errors.New("oops2") + err3 := errors.New("oops3") + return fmt.Errorf("%w, %T, %w", err1, err2, err3) +} + +func ErrorMultipleWraps() error { + err1 := errors.New("oops1") + err2 := errors.New("oops2") + err3 := errors.New("oops3") + return fmt.Errorf("%w, %w, %w", err1, err2, err3) +} + +func ErrorMultipleWrapsWithCustomError() error { + err1 := errors.New("oops1") + err2 := MyError{} + err3 := errors.New("oops3") + return fmt.Errorf("%w, %w, %w", err1, err2, err3) +} + +func ErrorStringFormat() error { + err := errors.New("oops") + return fmt.Errorf("error: %s", err.Error()) +} + +func ErrorStringFormatCustomError() error { + err := MyError{} + return fmt.Errorf("error: %s", err.Error()) +} + +func NotAnError() error { + err := "oops" + return fmt.Errorf("%v", err) +} + +type MyError struct{} + +func (MyError) Error() string { + return "oops" +} + +func ErrorIndexReset() error { + err := errors.New("oops1") + return fmt.Errorf("%[1]v %d %f %[1]v, %d, %f", err, 1, 2.2) +} + +func ErrorIndexResetGood() error { + err := errors.New("oops1") + return fmt.Errorf("%[1]w %d %f %[1]w, %d, %f", err, 1, 2.2) +} diff --git a/pkg/golinters/errorlint/testdata/fix/out/errorlint.go b/pkg/golinters/errorlint/testdata/fix/out/errorlint.go new file mode 100644 index 000000000000..5646e01a7ea4 --- /dev/null +++ b/pkg/golinters/errorlint/testdata/fix/out/errorlint.go @@ -0,0 +1,87 @@ +//golangcitest:args -Eerrorlint +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "errors" + "fmt" +) + +func Good() error { + err := errors.New("oops") + return fmt.Errorf("error: %w", err) +} + +func NonWrappingVerb() error { + err := errors.New("oops") + return fmt.Errorf("error: %w", err) +} + +func NonWrappingTVerb() error { + err := errors.New("oops") + return fmt.Errorf("error: %T", err) +} + +func DoubleNonWrappingVerb() error { + err := errors.New("oops") + return fmt.Errorf("%w %w", err, err) +} + +func ErrorOneWrap() error { + err1 := errors.New("oops1") + err2 := errors.New("oops2") + err3 := errors.New("oops3") + return fmt.Errorf("%w, %w, %w", err1, err2, err3) +} + +func ValidNonWrappingTVerb() error { + err1 := errors.New("oops1") + err2 := errors.New("oops2") + err3 := errors.New("oops3") + return fmt.Errorf("%w, %T, %w", err1, err2, err3) +} + +func ErrorMultipleWraps() error { + err1 := errors.New("oops1") + err2 := errors.New("oops2") + err3 := errors.New("oops3") + return fmt.Errorf("%w, %w, %w", err1, err2, err3) +} + +func ErrorMultipleWrapsWithCustomError() error { + err1 := errors.New("oops1") + err2 := MyError{} + err3 := errors.New("oops3") + return fmt.Errorf("%w, %w, %w", err1, err2, err3) +} + +func ErrorStringFormat() error { + err := errors.New("oops") + return fmt.Errorf("error: %s", err.Error()) +} + +func ErrorStringFormatCustomError() error { + err := MyError{} + return fmt.Errorf("error: %s", err.Error()) +} + +func NotAnError() error { + err := "oops" + return fmt.Errorf("%v", err) +} + +type MyError struct{} + +func (MyError) Error() string { + return "oops" +} + +func ErrorIndexReset() error { + err := errors.New("oops1") + return fmt.Errorf("%[1]w %d %f %[1]w, %d, %f", err, 1, 2.2) +} + +func ErrorIndexResetGood() error { + err := errors.New("oops1") + return fmt.Errorf("%[1]w %d %f %[1]w, %d, %f", err, 1, 2.2) +} From 4813ac725e1dbb2e5af7ce1847ddab60c4bf6f6f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:35:44 +0100 Subject: [PATCH 28/57] tests: autofix iface --- pkg/golinters/iface/iface_integration_test.go | 8 +++ pkg/golinters/iface/testdata/fix/in/iface.go | 69 +++++++++++++++++++ pkg/golinters/iface/testdata/fix/out/iface.go | 57 +++++++++++++++ pkg/golinters/iface/testdata/iface_fix.yml | 5 ++ 4 files changed, 139 insertions(+) create mode 100644 pkg/golinters/iface/testdata/fix/in/iface.go create mode 100644 pkg/golinters/iface/testdata/fix/out/iface.go create mode 100644 pkg/golinters/iface/testdata/iface_fix.yml diff --git a/pkg/golinters/iface/iface_integration_test.go b/pkg/golinters/iface/iface_integration_test.go index 01d587c8a285..f77c05b937f9 100644 --- a/pkg/golinters/iface/iface_integration_test.go +++ b/pkg/golinters/iface/iface_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/iface/testdata/fix/in/iface.go b/pkg/golinters/iface/testdata/fix/in/iface.go new file mode 100644 index 000000000000..0b5475bf3e42 --- /dev/null +++ b/pkg/golinters/iface/testdata/fix/in/iface.go @@ -0,0 +1,69 @@ +//golangcitest:args -Eiface +//golangcitest:config_path testdata/iface_fix.yml +//golangcitest:expected_exitcode 0 +package testdata + +import "fmt" + +// identical + +type Pinger interface { + Ping() error +} + +type Healthcheck interface { + Ping() error +} + +// opaque + +type Server interface { + Serve() error +} + +type server struct { + addr string +} + +func (s server) Serve() error { + return nil +} + +func NewServer(addr string) Server { + return &server{addr: addr} +} + +// unused + +type User struct { + ID string + Name string +} + +type UserRepository interface { + UserOf(id string) (*User, error) +} + +type UserRepositorySQL struct { +} + +func (r *UserRepositorySQL) UserOf(id string) (*User, error) { + return nil, nil +} + +type Granter interface { + Grant(permission string) error +} + +func AllowAll(g Granter) error { + return g.Grant("all") +} + +type Allower interface { + Allow(permission string) error +} + +func Allow(x any) { + _ = x.(Allower) + fmt.Println("allow") +} diff --git a/pkg/golinters/iface/testdata/fix/out/iface.go b/pkg/golinters/iface/testdata/fix/out/iface.go new file mode 100644 index 000000000000..d28f71f69350 --- /dev/null +++ b/pkg/golinters/iface/testdata/fix/out/iface.go @@ -0,0 +1,57 @@ +//golangcitest:args -Eiface +//golangcitest:config_path testdata/iface_fix.yml +//golangcitest:expected_exitcode 0 +package testdata + +import "fmt" + +// identical + +// opaque + +type Server interface { + Serve() error +} + +type server struct { + addr string +} + +func (s server) Serve() error { + return nil +} + +func NewServer(addr string) *server { + return &server{addr: addr} +} + +// unused + +type User struct { + ID string + Name string +} + +type UserRepositorySQL struct { +} + +func (r *UserRepositorySQL) UserOf(id string) (*User, error) { + return nil, nil +} + +type Granter interface { + Grant(permission string) error +} + +func AllowAll(g Granter) error { + return g.Grant("all") +} + +type Allower interface { + Allow(permission string) error +} + +func Allow(x any) { + _ = x.(Allower) + fmt.Println("allow") +} diff --git a/pkg/golinters/iface/testdata/iface_fix.yml b/pkg/golinters/iface/testdata/iface_fix.yml new file mode 100644 index 000000000000..79cac8560e3b --- /dev/null +++ b/pkg/golinters/iface/testdata/iface_fix.yml @@ -0,0 +1,5 @@ +linters-settings: + iface: + enable: + - unused + - opaque From ca2e133c175ce4bb58bb3681aa409bdbab598766 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:35:58 +0100 Subject: [PATCH 29/57] tests: autofix importas --- .../importas/importas_integration_test.go | 8 ++++++++ .../importas/testdata/fix/in/importas.go | 19 +++++++++++++++++++ .../importas/testdata/fix/out/importas.go | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 pkg/golinters/importas/testdata/fix/in/importas.go create mode 100644 pkg/golinters/importas/testdata/fix/out/importas.go diff --git a/pkg/golinters/importas/importas_integration_test.go b/pkg/golinters/importas/importas_integration_test.go index 42de9e166bc0..3441db4dbac0 100644 --- a/pkg/golinters/importas/importas_integration_test.go +++ b/pkg/golinters/importas/importas_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/importas/testdata/fix/in/importas.go b/pkg/golinters/importas/testdata/fix/in/importas.go new file mode 100644 index 000000000000..adeb21b76c4a --- /dev/null +++ b/pkg/golinters/importas/testdata/fix/in/importas.go @@ -0,0 +1,19 @@ +//golangcitest:args -Eimportas +//golangcitest:config_path testdata/importas.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + wrong_alias "fmt" + "os" + wrong_alias_again "os" + + wrong "golang.org/x/tools/go/analysis" +) + +func ImportAsWrongAlias() { + wrong_alias.Println("foo") + wrong_alias_again.Stdout.WriteString("bar") + os.Stdout.WriteString("test") + _ = wrong.Analyzer{} +} diff --git a/pkg/golinters/importas/testdata/fix/out/importas.go b/pkg/golinters/importas/testdata/fix/out/importas.go new file mode 100644 index 000000000000..091203adaef5 --- /dev/null +++ b/pkg/golinters/importas/testdata/fix/out/importas.go @@ -0,0 +1,19 @@ +//golangcitest:args -Eimportas +//golangcitest:config_path testdata/importas.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + fff "fmt" + "os" + std_os "os" + + ananas "golang.org/x/tools/go/analysis" +) + +func ImportAsWrongAlias() { + fff.Println("foo") + std_os.Stdout.WriteString("bar") + os.Stdout.WriteString("test") + _ = ananas.Analyzer{} +} From 54472f67d352e8a1ff8defea8c7c4b0dbe838d5d Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:36:29 +0100 Subject: [PATCH 30/57] tests: autofix intrange --- .../intrange/intrange_integration_test.go | 8 +++++++ .../intrange/testdata/fix/in/intrange.go | 22 +++++++++++++++++++ .../intrange/testdata/fix/out/intrange.go | 22 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 pkg/golinters/intrange/testdata/fix/in/intrange.go create mode 100644 pkg/golinters/intrange/testdata/fix/out/intrange.go diff --git a/pkg/golinters/intrange/intrange_integration_test.go b/pkg/golinters/intrange/intrange_integration_test.go index 1120a2eca5ed..d5e8aae77fc8 100644 --- a/pkg/golinters/intrange/intrange_integration_test.go +++ b/pkg/golinters/intrange/intrange_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/intrange/testdata/fix/in/intrange.go b/pkg/golinters/intrange/testdata/fix/in/intrange.go new file mode 100644 index 000000000000..ead491dcdb0a --- /dev/null +++ b/pkg/golinters/intrange/testdata/fix/in/intrange.go @@ -0,0 +1,22 @@ +//go:build go1.22 + +//golangcitest:args -Eintrange +//golangcitest:expected_exitcode 0 +package testdata + +import "math" + +func CheckIntrange() { + for i := 0; i < 10; i++ { + } + + for i := uint8(0); i < math.MaxInt8; i++ { + } + + for i := 0; i < 10; i += 2 { + } + + for i := 0; i < 10; i++ { + i += 1 + } +} diff --git a/pkg/golinters/intrange/testdata/fix/out/intrange.go b/pkg/golinters/intrange/testdata/fix/out/intrange.go new file mode 100644 index 000000000000..28ef37b36a24 --- /dev/null +++ b/pkg/golinters/intrange/testdata/fix/out/intrange.go @@ -0,0 +1,22 @@ +//go:build go1.22 + +//golangcitest:args -Eintrange +//golangcitest:expected_exitcode 0 +package testdata + +import "math" + +func CheckIntrange() { + for range 10 { + } + + for range uint8(math.MaxInt8) { + } + + for i := 0; i < 10; i += 2 { + } + + for i := 0; i < 10; i++ { + i += 1 + } +} From 46c6a38ae529c19fc2d7e8c2e7fb8b456c69f002 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:36:49 +0100 Subject: [PATCH 31/57] tests: autofix nakedret --- .../nakedret/nakedret_integration_test.go | 8 +++ .../nakedret/testdata/fix/in/nakedret.go | 72 +++++++++++++++++++ .../nakedret/testdata/fix/out/nakedret.go | 72 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 pkg/golinters/nakedret/testdata/fix/in/nakedret.go create mode 100644 pkg/golinters/nakedret/testdata/fix/out/nakedret.go diff --git a/pkg/golinters/nakedret/nakedret_integration_test.go b/pkg/golinters/nakedret/nakedret_integration_test.go index 2c081425adce..ac0edaf84753 100644 --- a/pkg/golinters/nakedret/nakedret_integration_test.go +++ b/pkg/golinters/nakedret/nakedret_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/nakedret/testdata/fix/in/nakedret.go b/pkg/golinters/nakedret/testdata/fix/in/nakedret.go new file mode 100644 index 000000000000..91ededf10e1a --- /dev/null +++ b/pkg/golinters/nakedret/testdata/fix/in/nakedret.go @@ -0,0 +1,72 @@ +//golangcitest:args -Enakedret +//golangcitest:expected_exitcode 0 +package testdata + +import "fmt" + +func NakedretIssue() (a int, b string) { + if a > 0 { + return + } + + fmt.Println("nakedret") + + if b == "" { + return 0, "0" + } + + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + + // len of this function is 33 + return +} + +func NoNakedretIssue() (a int, b string) { + if a > 0 { + return + } + + if b == "" { + return 0, "0" + } + + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + + // len of this function is 30 + return +} diff --git a/pkg/golinters/nakedret/testdata/fix/out/nakedret.go b/pkg/golinters/nakedret/testdata/fix/out/nakedret.go new file mode 100644 index 000000000000..dd5fb63e3f1e --- /dev/null +++ b/pkg/golinters/nakedret/testdata/fix/out/nakedret.go @@ -0,0 +1,72 @@ +//golangcitest:args -Enakedret +//golangcitest:expected_exitcode 0 +package testdata + +import "fmt" + +func NakedretIssue() (a int, b string) { + if a > 0 { + return a, b + } + + fmt.Println("nakedret") + + if b == "" { + return 0, "0" + } + + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + + // len of this function is 33 + return a, b +} + +func NoNakedretIssue() (a int, b string) { + if a > 0 { + return + } + + if b == "" { + return 0, "0" + } + + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + + // len of this function is 30 + return +} From ba516f4a4b199ef3a2d916f4e745c6bb92cf55c3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:37:02 +0100 Subject: [PATCH 32/57] tests: autofix nlreturn --- .../nlreturn/nlreturn_integration_test.go | 8 + .../nlreturn/testdata/fix/in/nlreturn.go | 162 ++++++++++++++++ .../nlreturn/testdata/fix/out/nlreturn.go | 173 ++++++++++++++++++ .../nlreturn/testdata/nlreturn-block-size.go | 2 +- .../{nlreturn.yml => nlreturn-block-size.yml} | 0 5 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 pkg/golinters/nlreturn/testdata/fix/in/nlreturn.go create mode 100644 pkg/golinters/nlreturn/testdata/fix/out/nlreturn.go rename pkg/golinters/nlreturn/testdata/{nlreturn.yml => nlreturn-block-size.yml} (100%) diff --git a/pkg/golinters/nlreturn/nlreturn_integration_test.go b/pkg/golinters/nlreturn/nlreturn_integration_test.go index edb97def4db8..55522191f4cb 100644 --- a/pkg/golinters/nlreturn/nlreturn_integration_test.go +++ b/pkg/golinters/nlreturn/nlreturn_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/nlreturn/testdata/fix/in/nlreturn.go b/pkg/golinters/nlreturn/testdata/fix/in/nlreturn.go new file mode 100644 index 000000000000..63c8d56f919e --- /dev/null +++ b/pkg/golinters/nlreturn/testdata/fix/in/nlreturn.go @@ -0,0 +1,162 @@ +//golangcitest:args -Enlreturn +//golangcitest:expected_exitcode 0 +package testdata + +func cha() { + ch := make(chan interface{}) + ch1 := make(chan interface{}) + ch2 := make(chan interface{}) + + select { + case <-ch: + return + + case <-ch1: + { + a := 1 + _ = a + { + a := 1 + _ = a + return // want "return with no blank line before" + } + + return + } + + return + + case <-ch2: + { + a := 1 + _ = a + return // want "return with no blank line before" + } + return // want "return with no blank line before" + } +} + +func baz() { + switch 0 { + case 0: + a := 1 + _ = a + fallthrough // want "fallthrough with no blank line before" + case 1: + a := 1 + _ = a + break // want "break with no blank line before" + case 2: + break + } +} + +func foo() int { + v := []int{} + for range v { + return 0 + } + + for range v { + for range v { + return 0 + } + return 0 // want "return with no blank line before" + } + + o := []int{ + 0, 1, + } + + return o[0] +} + +func bar() int { + o := 1 + if o == 1 { + if o == 0 { + return 1 + } + return 0 // want "return with no blank line before" + } + + return o +} + +func main() { + return +} + +func bugNoAssignSmthHandling() string { + switch 0 { + case 0: + o := struct { + foo string + }{ + "foo", + } + return o.foo // want "return with no blank line before" + + case 1: + o := struct { + foo string + }{ + "foo", + } + + return o.foo + } + + return "" +} + +func bugNoExprSmthHandling(string) { + switch 0 { + case 0: + bugNoExprSmthHandling( + "", + ) + return // want "return with no blank line before" + + case 1: + bugNoExprSmthHandling( + "", + ) + + return + } +} + +func bugNoDeferSmthHandling(string) { + switch 0 { + case 0: + defer bugNoDeferSmthHandling( + "", + ) + return // want "return with no blank line before" + + case 1: + defer bugNoDeferSmthHandling( + "", + ) + + return + } +} + +func bugNoGoSmthHandling(string) { + switch 0 { + case 0: + go bugNoGoSmthHandling( + "", + ) + return // want "return with no blank line before" + + case 1: + go bugNoGoSmthHandling( + "", + ) + + return + } +} diff --git a/pkg/golinters/nlreturn/testdata/fix/out/nlreturn.go b/pkg/golinters/nlreturn/testdata/fix/out/nlreturn.go new file mode 100644 index 000000000000..f12c7bd1680d --- /dev/null +++ b/pkg/golinters/nlreturn/testdata/fix/out/nlreturn.go @@ -0,0 +1,173 @@ +//golangcitest:args -Enlreturn +//golangcitest:expected_exitcode 0 +package testdata + +func cha() { + ch := make(chan interface{}) + ch1 := make(chan interface{}) + ch2 := make(chan interface{}) + + select { + case <-ch: + return + + case <-ch1: + { + a := 1 + _ = a + { + a := 1 + _ = a + + return // want "return with no blank line before" + } + + return + } + + return + + case <-ch2: + { + a := 1 + _ = a + + return // want "return with no blank line before" + } + + return // want "return with no blank line before" + } +} + +func baz() { + switch 0 { + case 0: + a := 1 + _ = a + + fallthrough // want "fallthrough with no blank line before" + case 1: + a := 1 + _ = a + + break // want "break with no blank line before" + case 2: + break + } +} + +func foo() int { + v := []int{} + for range v { + return 0 + } + + for range v { + for range v { + return 0 + } + + return 0 // want "return with no blank line before" + } + + o := []int{ + 0, 1, + } + + return o[0] +} + +func bar() int { + o := 1 + if o == 1 { + if o == 0 { + return 1 + } + + return 0 // want "return with no blank line before" + } + + return o +} + +func main() { + return +} + +func bugNoAssignSmthHandling() string { + switch 0 { + case 0: + o := struct { + foo string + }{ + "foo", + } + + return o.foo // want "return with no blank line before" + + case 1: + o := struct { + foo string + }{ + "foo", + } + + return o.foo + } + + return "" +} + +func bugNoExprSmthHandling(string) { + switch 0 { + case 0: + bugNoExprSmthHandling( + "", + ) + + return // want "return with no blank line before" + + case 1: + bugNoExprSmthHandling( + "", + ) + + return + } +} + +func bugNoDeferSmthHandling(string) { + switch 0 { + case 0: + defer bugNoDeferSmthHandling( + "", + ) + + return // want "return with no blank line before" + + case 1: + defer bugNoDeferSmthHandling( + "", + ) + + return + } +} + +func bugNoGoSmthHandling(string) { + switch 0 { + case 0: + go bugNoGoSmthHandling( + "", + ) + + return // want "return with no blank line before" + + case 1: + go bugNoGoSmthHandling( + "", + ) + + return + } +} diff --git a/pkg/golinters/nlreturn/testdata/nlreturn-block-size.go b/pkg/golinters/nlreturn/testdata/nlreturn-block-size.go index d58ad81c57cf..bc3dbedf7b72 100644 --- a/pkg/golinters/nlreturn/testdata/nlreturn-block-size.go +++ b/pkg/golinters/nlreturn/testdata/nlreturn-block-size.go @@ -1,5 +1,5 @@ //golangcitest:args -Enlreturn -//golangcitest:config_path testdata/nlreturn.yml +//golangcitest:config_path testdata/nlreturn-block-size.yml package testdata func foo0(n int) int { diff --git a/pkg/golinters/nlreturn/testdata/nlreturn.yml b/pkg/golinters/nlreturn/testdata/nlreturn-block-size.yml similarity index 100% rename from pkg/golinters/nlreturn/testdata/nlreturn.yml rename to pkg/golinters/nlreturn/testdata/nlreturn-block-size.yml From 159bf028d572bee2a536533c7496d9b93ee40b4c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:37:18 +0100 Subject: [PATCH 33/57] tests: autofix perfsprint --- .../perfsprint/perfsprint_integration_test.go | 8 +++ .../perfsprint/testdata/fix/in/perfsprint.go | 54 +++++++++++++++++++ .../perfsprint/testdata/fix/out/perfsprint.go | 54 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 pkg/golinters/perfsprint/testdata/fix/in/perfsprint.go create mode 100644 pkg/golinters/perfsprint/testdata/fix/out/perfsprint.go diff --git a/pkg/golinters/perfsprint/perfsprint_integration_test.go b/pkg/golinters/perfsprint/perfsprint_integration_test.go index a94b87573b14..038b72d6cb41 100644 --- a/pkg/golinters/perfsprint/perfsprint_integration_test.go +++ b/pkg/golinters/perfsprint/perfsprint_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/perfsprint/testdata/fix/in/perfsprint.go b/pkg/golinters/perfsprint/testdata/fix/in/perfsprint.go new file mode 100644 index 000000000000..845028864b47 --- /dev/null +++ b/pkg/golinters/perfsprint/testdata/fix/in/perfsprint.go @@ -0,0 +1,54 @@ +//golangcitest:args -Eperfsprint +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "fmt" +) + +func TestPerfsprint() { + var ( + s string + err error + b bool + i int + i64 int64 + ui uint + ) + + fmt.Sprintf("%s", s) // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprint(s) // want "fmt.Sprint can be replaced with just using the string" + fmt.Sprintf("%s", err) + fmt.Sprint(err) + fmt.Sprintf("%t", b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprint(b) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%d", i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(i) // want "fmt.Sprint can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i64) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprint(i64) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" + fmt.Sprintf("%d", ui) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%x", []byte{'a'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + fmt.Errorf("hello") // want "fmt.Errorf can be replaced with errors.New" + fmt.Sprintf("Hello %s", s) // want "fmt.Sprintf can be replaced with string concatenation" + + fmt.Sprint("test", 42) + fmt.Sprint(42, 42) + fmt.Sprintf("test") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%v") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%d") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%d", 42, 42) + fmt.Sprintf("%#d", 42) + fmt.Sprintf("value %d", 42) + fmt.Sprintf("val%d", 42) + fmt.Sprintf("%s %v", "hello", "world") + fmt.Sprintf("%#v", 42) + fmt.Sprintf("%T", struct{ string }{}) + fmt.Sprintf("%%v", 42) + fmt.Sprintf("%3d", 42) + fmt.Sprintf("% d", 42) + fmt.Sprintf("%-10d", 42) + fmt.Sprintf("%[2]d %[1]d\n", 11, 22) + fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6) + fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) +} diff --git a/pkg/golinters/perfsprint/testdata/fix/out/perfsprint.go b/pkg/golinters/perfsprint/testdata/fix/out/perfsprint.go new file mode 100644 index 000000000000..20d4bca85ece --- /dev/null +++ b/pkg/golinters/perfsprint/testdata/fix/out/perfsprint.go @@ -0,0 +1,54 @@ +//golangcitest:args -Eperfsprint +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "fmt" +) + +func TestPerfsprint() { + var ( + s string + err error + b bool + i int + i64 int64 + ui uint + ) + + s // want "fmt.Sprintf can be replaced with just using the string" + s // want "fmt.Sprint can be replaced with just using the string" + fmt.Sprintf("%s", err) + fmt.Sprint(err) + strconv.FormatBool(b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + strconv.FormatBool(b) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" + strconv.Itoa(i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + strconv.Itoa(i) // want "fmt.Sprint can be replaced with faster strconv.Itoa" + strconv.FormatInt(i64, 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + strconv.FormatInt(i64, 10) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" + strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + strconv.FormatUint(uint64(ui), 10) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" + hex.EncodeToString([]byte{'a'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + errors.New("hello") // want "fmt.Errorf can be replaced with errors.New" + "Hello " + s // want "fmt.Sprintf can be replaced with string concatenation" + + fmt.Sprint("test", 42) + fmt.Sprint(42, 42) + "test" // want "fmt.Sprintf can be replaced with just using the string" + "%v" // want "fmt.Sprintf can be replaced with just using the string" + "%d" // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%d", 42, 42) + fmt.Sprintf("%#d", 42) + fmt.Sprintf("value %d", 42) + fmt.Sprintf("val%d", 42) + fmt.Sprintf("%s %v", "hello", "world") + fmt.Sprintf("%#v", 42) + fmt.Sprintf("%T", struct{ string }{}) + fmt.Sprintf("%%v", 42) + fmt.Sprintf("%3d", 42) + fmt.Sprintf("% d", 42) + fmt.Sprintf("%-10d", 42) + fmt.Sprintf("%[2]d %[1]d\n", 11, 22) + fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6) + fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) +} From a6426404e99026dfe276b4761f86b406c6e308b2 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:37:40 +0100 Subject: [PATCH 34/57] tests: autofix testifylint --- .../testdata/fix/in/testifylint.go | 97 +++++++++++++++++++ .../testdata/fix/out/testifylint.go | 94 ++++++++++++++++++ .../testifylint_integration_test.go | 8 ++ 3 files changed, 199 insertions(+) create mode 100644 pkg/golinters/testifylint/testdata/fix/in/testifylint.go create mode 100644 pkg/golinters/testifylint/testdata/fix/out/testifylint.go diff --git a/pkg/golinters/testifylint/testdata/fix/in/testifylint.go b/pkg/golinters/testifylint/testdata/fix/in/testifylint.go new file mode 100644 index 000000000000..a79161f9ca7a --- /dev/null +++ b/pkg/golinters/testifylint/testdata/fix/in/testifylint.go @@ -0,0 +1,97 @@ +//golangcitest:args -Etestifylint +package testdata + +import ( + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type Bool bool + +func TestTestifylint(t *testing.T) { + var ( + predicate bool + resultInt int + resultFloat float64 + arr []string + err error + ) + + assert.Equal(t, predicate, true) // want "bool-compare: use assert\\.True" + assert.Equal(t, Bool(predicate), false) // want "bool-compare: use assert\\.False" + assert.True(t, resultInt == 1) // want "compares: use assert\\.Equal" + assert.Equal(t, len(arr), 0) // want "empty: use assert\\.Empty" + assert.Error(t, err, io.EOF) // want "error-is-as: invalid usage of assert\\.Error, use assert\\.ErrorIs instead" + assert.Nil(t, err) // want "error-nil: use assert\\.NoError" + assert.Equal(t, resultInt, 42) // want "expected-actual: need to reverse actual and expected values" + assert.Equal(t, resultFloat, 42.42) // want "float-compare: use assert\\.InEpsilon \\(or InDelta\\)" + assert.Equal(t, len(arr), 10) // want "len: use assert\\.Len" + + assert.True(t, predicate) + assert.Equal(t, resultInt, 1) // want "expected-actual: need to reverse actual and expected values" + assert.Empty(t, arr) + assert.ErrorIs(t, err, io.EOF) // want "require-error: for error assertions use require" + assert.NoError(t, err) // want "require-error: for error assertions use require" + assert.Equal(t, 42, resultInt) + assert.InEpsilon(t, 42.42, resultFloat, 0.0001) + assert.Len(t, arr, 10) + + require.ErrorIs(t, err, io.EOF) + require.NoError(t, err) + + t.Run("formatted", func(t *testing.T) { + assert.Equal(t, predicate, true, "message") // want "bool-compare: use assert\\.True" + assert.Equal(t, predicate, true, "message %d", 42) // want "bool-compare: use assert\\.True" + assert.Equalf(t, predicate, true, "message") // want "bool-compare: use assert\\.Truef" + assert.Equalf(t, predicate, true, "message %d", 42) // want "bool-compare: use assert\\.Truef" + + assert.Equal(t, 1, 2, fmt.Sprintf("msg")) // want "formatter: remove unnecessary fmt\\.Sprintf" + assert.Equalf(t, 1, 2, "msg with arg", "arg") // want "formatter: assert\\.Equalf call has arguments but no formatting directives" + }) + + assert.Equal(t, arr, nil) // want "nil-compare: use assert\\.Nil" + assert.Nil(t, arr) + + go func() { + if assert.Error(t, err) { + require.ErrorIs(t, err, io.EOF) // want "go-require: require must only be used in the goroutine running the test function" + } + }() +} + +type SuiteExample struct { + suite.Suite +} + +func TestSuiteExample(t *testing.T) { + suite.Run(t, new(SuiteExample)) +} + +func (s *SuiteExample) TestAll() { + var b bool + s.Assert().True(b) // want "suite-extra-assert-call: need to simplify the assertion to s\\.True" +} + +func (s *SuiteExample) TestOne() { + s.T().Parallel() // want "suite-broken-parallel: testify v1 does not support suite's parallel tests and subtests" + + s.T().Run("subtest", func(t *testing.T) { // want "suite-subtest-run: use s\\.Run to run subtest" + t.Parallel() // want "suite-broken-parallel: testify v1 does not support suite's parallel tests and subtests" + + assert.Equal(s.T(), 1, 2) // want "suite-dont-use-pkg: use s\\.Equal" + s.Equal(1, 2) + }) + + s.Run("subtest", func() { + s.T().Parallel() // want "suite-broken-parallel: testify v1 does not support suite's parallel tests and subtests" + s.Equal(1, 2) + }) + + var b bool + s.Assert().True(b) // want "suite-extra-assert-call: need to simplify the assertion to s\\.True" +} diff --git a/pkg/golinters/testifylint/testdata/fix/out/testifylint.go b/pkg/golinters/testifylint/testdata/fix/out/testifylint.go new file mode 100644 index 000000000000..5d1f101d4e2c --- /dev/null +++ b/pkg/golinters/testifylint/testdata/fix/out/testifylint.go @@ -0,0 +1,94 @@ +//golangcitest:args -Etestifylint +package testdata + +import ( + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type Bool bool + +func TestTestifylint(t *testing.T) { + var ( + predicate bool + resultInt int + resultFloat float64 + arr []string + err error + ) + + assert.True(t, predicate) // want "bool-compare: use assert\\.True" + assert.False(t, bool(Bool(predicate))) // want "bool-compare: use assert\\.False" + assert.Equal(t, resultInt, 1) // want "compares: use assert\\.Equal" + assert.Empty(t, arr) // want "empty: use assert\\.Empty" + assert.ErrorIs(t, err, io.EOF) // want "error-is-as: invalid usage of assert\\.Error, use assert\\.ErrorIs instead" + assert.NoError(t, err) // want "error-nil: use assert\\.NoError" + assert.Equal(t, 42, resultInt) // want "expected-actual: need to reverse actual and expected values" + assert.Equal(t, resultFloat, 42.42) // want "float-compare: use assert\\.InEpsilon \\(or InDelta\\)" + assert.Len(t, arr, 10) // want "len: use assert\\.Len" + + assert.True(t, predicate) + assert.Equal(t, 1, resultInt) // want "expected-actual: need to reverse actual and expected values" + assert.Empty(t, arr) + assert.ErrorIs(t, err, io.EOF) // want "require-error: for error assertions use require" + assert.NoError(t, err) // want "require-error: for error assertions use require" + assert.Equal(t, 42, resultInt) + assert.InEpsilon(t, 42.42, resultFloat, 0.0001) + assert.Len(t, arr, 10) + + require.ErrorIs(t, err, io.EOF) + require.NoError(t, err) + + t.Run("formatted", func(t *testing.T) { + assert.True(t, predicate, "message") // want "bool-compare: use assert\\.True" + assert.True(t, predicate, "message %d", 42) // want "bool-compare: use assert\\.True" + assert.Truef(t, predicate, "message") // want "bool-compare: use assert\\.Truef" + assert.Truef(t, predicate, "message %d", 42) // want "bool-compare: use assert\\.Truef" + + assert.Equal(t, 1, 2, "msg") // want "formatter: remove unnecessary fmt\\.Sprintf" + assert.Equalf(t, 1, 2, "msg with arg", "arg") // want "formatter: assert\\.Equalf call has arguments but no formatting directives" + }) + + assert.Nil(t, arr) // want "nil-compare: use assert\\.Nil" + assert.Nil(t, arr) + + go func() { + if assert.Error(t, err) { + require.ErrorIs(t, err, io.EOF) // want "go-require: require must only be used in the goroutine running the test function" + } + }() +} + +type SuiteExample struct { + suite.Suite +} + +func TestSuiteExample(t *testing.T) { + suite.Run(t, new(SuiteExample)) +} + +func (s *SuiteExample) TestAll() { + var b bool + s.True(b) // want "suite-extra-assert-call: need to simplify the assertion to s\\.True" +} + +func (s *SuiteExample) TestOne() { + + s.T().Run("subtest", func(t *testing.T) { // want "suite-subtest-run: use s\\.Run to run subtest" + + s.Equal(1, 2) // want "suite-dont-use-pkg: use s\\.Equal" + s.Equal(1, 2) + }) + + s.Run("subtest", func() { + s.Equal(1, 2) + }) + + var b bool + s.True(b) // want "suite-extra-assert-call: need to simplify the assertion to s\\.True" +} diff --git a/pkg/golinters/testifylint/testifylint_integration_test.go b/pkg/golinters/testifylint/testifylint_integration_test.go index 0e6966f45187..0bb1fb3e026c 100644 --- a/pkg/golinters/testifylint/testifylint_integration_test.go +++ b/pkg/golinters/testifylint/testifylint_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} From 26c61a8adcfbb82d5fa6e37e8708d12561ba3b00 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:37:56 +0100 Subject: [PATCH 35/57] tests: autofix wsl --- pkg/golinters/wsl/testdata/fix/in/wsl.go | 220 ++++++++++++++++++++ pkg/golinters/wsl/testdata/fix/out/wsl.go | 232 ++++++++++++++++++++++ pkg/golinters/wsl/wsl_integration_test.go | 8 + 3 files changed, 460 insertions(+) create mode 100644 pkg/golinters/wsl/testdata/fix/in/wsl.go create mode 100644 pkg/golinters/wsl/testdata/fix/out/wsl.go diff --git a/pkg/golinters/wsl/testdata/fix/in/wsl.go b/pkg/golinters/wsl/testdata/fix/in/wsl.go new file mode 100644 index 000000000000..9a1982004c2f --- /dev/null +++ b/pkg/golinters/wsl/testdata/fix/in/wsl.go @@ -0,0 +1,220 @@ +//golangcitest:args -Ewsl +//golangcitest:config_path testdata/wsl.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "context" + "fmt" +) + +func main() { + var ( + y = 0 + ) + if y < 1 { + fmt.Println("tight") + } + + thisIsNotUsedInIf := true + if 2 > 1 { + return + } + + one := 1 + two := 2 + three := 3 + if three == 3 { + fmt.Println("too many cuddled assignments", one, two, thisIsNotUsedInIf) + } + + var a = "a" + var b = "b" + + if true { + return + } + if false { + return + } + + for i := range make([]int, 10) { + fmt.Println(i) + fmt.Println(i + i) + continue + } + + assignOne := a + fmt.Println(assignOne) + assignTwo := b + fmt.Println(assignTwo) + + _, cf1 := context.WithCancel(context.Background()) + _, cf2 := context.WithCancel(context.Background()) + defer cf1() + defer cf2() + + err := multiline( + "spanning", + "multiple", + ) + if err != nil { + panic(err) + } + + notErr := multiline( + "spanning", + "multiple", + ) + if err != nil { + panic("not from the line above") + } + + // This is OK since we use a variable from the line above, even if we don't + // check it with the if. + xx := notErr + if err != nil { + panic(xx) + } +} + +func multiline(s ...string) error { + return nil +} + +func f1() int { + x := 1 + return x +} + +func f2() int { + x := 1 + y := 3 + return x + y +} + +func f3() int { + sum := 0 + for _, v := range []int{2, 4, 8} { + sum += v + } + + notSum := 0 + for _, v := range []int{1, 2, 4} { + sum += v + } + + return sum + notSum +} + +func onelineShouldNotError() error { return nil } + +func multilineCase() { + // Multiline cases + switch { + case true, + false: + fmt.Println("ok") + case false || + true: + fmt.Println("ok") + case true, + false: + fmt.Println("ok") + } +} + +func sliceExpr() { + // Index- and slice expressions. + var aSlice = []int{1, 2, 3} + + start := 2 + if v := aSlice[start]; v == 1 { + fmt.Println("ok") + } + + notOk := 1 + if v := aSlice[start]; v == 1 { + fmt.Println("notOk") + fmt.Println(notOk) + } + + end := 2 + if len(aSlice[start:end]) > 2 { + fmt.Println("ok") + } +} + +func indexExpr() { + var aMap = map[string]struct{}{"key": {}} + + key := "key" + if k, ok := aMap[key]; ok { + fmt.Println(k) + } + + xxx := "xxx" + if _, ok := aMap[key]; ok { + fmt.Println("not ok") + fmt.Println(xxx) + } +} + +func allowTrailing(i int) { + switch i { + case 1: + fmt.Println("one") + + case 2: + fmt.Println("two") + // Comments OK too! + case 3: + fmt.Println("three") + } +} + +// ExampleSomeOutput simulates an example function. +func ExampleSomeOutput() { + fmt.Println("Hello, world") + + // Output: + // Hello, world +} + +func IncDecStmt() { + counter := 0 + for range make([]int, 5) { + counter++ + } + + type t struct { + counter int + } + + x := t{5} + + x.counter-- + if x.counter > 0 { + fmt.Println("not yet 0") + } +} + +func AnonymousBlock() { + func(a, b int) { + + fmt.Println(a + b) + }(1, 1) +} + +func MultilineComment() { + if true { + /* + Ok to start block with + a + long + multiline + cmoment + */ + fmt.Println("true") + } +} diff --git a/pkg/golinters/wsl/testdata/fix/out/wsl.go b/pkg/golinters/wsl/testdata/fix/out/wsl.go new file mode 100644 index 000000000000..68fc20f5f487 --- /dev/null +++ b/pkg/golinters/wsl/testdata/fix/out/wsl.go @@ -0,0 +1,232 @@ +//golangcitest:args -Ewsl +//golangcitest:config_path testdata/wsl.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "context" + "fmt" +) + +func main() { + var ( + y = 0 + ) + + if y < 1 { + fmt.Println("tight") + } + + thisIsNotUsedInIf := true + + if 2 > 1 { + return + } + + one := 1 + two := 2 + three := 3 + + if three == 3 { + fmt.Println("too many cuddled assignments", one, two, thisIsNotUsedInIf) + } + + var a = "a" + + var b = "b" + + if true { + return + } + + if false { + return + } + + for i := range make([]int, 10) { + fmt.Println(i) + fmt.Println(i + i) + + continue + } + + assignOne := a + fmt.Println(assignOne) + + assignTwo := b + fmt.Println(assignTwo) + + _, cf1 := context.WithCancel(context.Background()) + _, cf2 := context.WithCancel(context.Background()) + + defer cf1() + defer cf2() + + err := multiline( + "spanning", + "multiple", + ) + if err != nil { + panic(err) + } + + notErr := multiline( + "spanning", + "multiple", + ) + + if err != nil { + panic("not from the line above") + } + + // This is OK since we use a variable from the line above, even if we don't + // check it with the if. + xx := notErr + if err != nil { + panic(xx) + } +} + +func multiline(s ...string) error { + return nil +} + +func f1() int { + x := 1 + return x +} + +func f2() int { + x := 1 + y := 3 + + return x + y +} + +func f3() int { + sum := 0 + for _, v := range []int{2, 4, 8} { + sum += v + } + + notSum := 0 + + for _, v := range []int{1, 2, 4} { + sum += v + } + + return sum + notSum +} + +func onelineShouldNotError() error { return nil } + +func multilineCase() { + // Multiline cases + switch { + case true, + false: + fmt.Println("ok") + case false || + true: + fmt.Println("ok") + case true, + false: + fmt.Println("ok") + } +} + +func sliceExpr() { + // Index- and slice expressions. + var aSlice = []int{1, 2, 3} + + start := 2 + if v := aSlice[start]; v == 1 { + fmt.Println("ok") + } + + notOk := 1 + + if v := aSlice[start]; v == 1 { + fmt.Println("notOk") + fmt.Println(notOk) + } + + end := 2 + if len(aSlice[start:end]) > 2 { + fmt.Println("ok") + } +} + +func indexExpr() { + var aMap = map[string]struct{}{"key": {}} + + key := "key" + if k, ok := aMap[key]; ok { + fmt.Println(k) + } + + xxx := "xxx" + + if _, ok := aMap[key]; ok { + fmt.Println("not ok") + fmt.Println(xxx) + } +} + +func allowTrailing(i int) { + switch i { + case 1: + fmt.Println("one") + + case 2: + fmt.Println("two") + // Comments OK too! + case 3: + fmt.Println("three") + } +} + +// ExampleSomeOutput simulates an example function. +func ExampleSomeOutput() { + fmt.Println("Hello, world") + + // Output: + // Hello, world +} + +func IncDecStmt() { + counter := 0 + for range make([]int, 5) { + counter++ + } + + type t struct { + counter int + } + + x := t{5} + + x.counter-- + if x.counter > 0 { + fmt.Println("not yet 0") + } +} + +func AnonymousBlock() { + func(a, b int) { + fmt.Println(a + b) + }(1, 1) +} + +func MultilineComment() { + if true { + /* + Ok to start block with + a + long + multiline + cmoment + */ + fmt.Println("true") + } +} diff --git a/pkg/golinters/wsl/wsl_integration_test.go b/pkg/golinters/wsl/wsl_integration_test.go index e9de29926090..98dfa49258eb 100644 --- a/pkg/golinters/wsl/wsl_integration_test.go +++ b/pkg/golinters/wsl/wsl_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} From 65fd89702934a707be623f380b538889a97e2664 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:38:23 +0100 Subject: [PATCH 36/57] tests: autofix govet --- pkg/golinters/govet/govet_integration_test.go | 8 ++++++ pkg/golinters/govet/testdata/fix/in/govet.go | 25 +++++++++++++++++++ pkg/golinters/govet/testdata/fix/out/govet.go | 25 +++++++++++++++++++ pkg/golinters/govet/testdata/govet_fix.yml | 13 ++++++++++ 4 files changed, 71 insertions(+) create mode 100644 pkg/golinters/govet/testdata/fix/in/govet.go create mode 100644 pkg/golinters/govet/testdata/fix/out/govet.go create mode 100644 pkg/golinters/govet/testdata/govet_fix.yml diff --git a/pkg/golinters/govet/govet_integration_test.go b/pkg/golinters/govet/govet_integration_test.go index 26cab9c8c3c7..65256d7ffe05 100644 --- a/pkg/golinters/govet/govet_integration_test.go +++ b/pkg/golinters/govet/govet_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/govet/testdata/fix/in/govet.go b/pkg/golinters/govet/testdata/fix/in/govet.go new file mode 100644 index 000000000000..d7080eff8a79 --- /dev/null +++ b/pkg/golinters/govet/testdata/fix/in/govet.go @@ -0,0 +1,25 @@ +//golangcitest:args -Egovet +//golangcitest:config_path testdata/govet_fix.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "fmt" + "log" + "os" +) + +type Foo struct { + A []string + B bool + C string + D int8 + E int32 +} + +func nonConstantFormat(s string) { + fmt.Printf(s) + fmt.Printf(s, "arg") + fmt.Fprintf(os.Stderr, s) + log.Printf(s) +} diff --git a/pkg/golinters/govet/testdata/fix/out/govet.go b/pkg/golinters/govet/testdata/fix/out/govet.go new file mode 100644 index 000000000000..2751e37c6771 --- /dev/null +++ b/pkg/golinters/govet/testdata/fix/out/govet.go @@ -0,0 +1,25 @@ +//golangcitest:args -Egovet +//golangcitest:config_path testdata/govet_fix.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "fmt" + "log" + "os" +) + +type Foo struct { + C string + A []string + E int32 + B bool + D int8 +} + +func nonConstantFormat(s string) { + fmt.Printf("%s", s) + fmt.Printf(s, "arg") + fmt.Fprintf(os.Stderr, "%s", s) + log.Printf("%s", s) +} diff --git a/pkg/golinters/govet/testdata/govet_fix.yml b/pkg/golinters/govet/testdata/govet_fix.yml new file mode 100644 index 000000000000..5fa5a09bb44a --- /dev/null +++ b/pkg/golinters/govet/testdata/govet_fix.yml @@ -0,0 +1,13 @@ +linters-settings: + govet: + enable: + - assign + - composites + - fieldalignment + - findcall + - printf + - sigchanyzer + - sortslice + - stringintconv + - timeformat + - unreachable From fe005f2ff2f3261e6d3c1f9c25f9f8c381fe670f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:38:35 +0100 Subject: [PATCH 37/57] tests: autofix gosimple --- pkg/golinters/gosimple/gosimple_integration_test.go | 8 ++++++++ pkg/golinters/gosimple/testdata/fix/in/gosimple.go | 10 ++++++++++ pkg/golinters/gosimple/testdata/fix/out/gosimple.go | 8 ++++++++ 3 files changed, 26 insertions(+) create mode 100644 pkg/golinters/gosimple/testdata/fix/in/gosimple.go create mode 100644 pkg/golinters/gosimple/testdata/fix/out/gosimple.go diff --git a/pkg/golinters/gosimple/gosimple_integration_test.go b/pkg/golinters/gosimple/gosimple_integration_test.go index 34622960fb1b..9f8f583f032c 100644 --- a/pkg/golinters/gosimple/gosimple_integration_test.go +++ b/pkg/golinters/gosimple/gosimple_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/gosimple/testdata/fix/in/gosimple.go b/pkg/golinters/gosimple/testdata/fix/in/gosimple.go new file mode 100644 index 000000000000..8dca57b42cee --- /dev/null +++ b/pkg/golinters/gosimple/testdata/fix/in/gosimple.go @@ -0,0 +1,10 @@ +//golangcitest:args -Egosimple +//golangcitest:expected_exitcode 0 +package testdata + +func _(src []string) { + var dst []string + for i, x := range src { + dst[i] = x + } +} diff --git a/pkg/golinters/gosimple/testdata/fix/out/gosimple.go b/pkg/golinters/gosimple/testdata/fix/out/gosimple.go new file mode 100644 index 000000000000..b03a15c5f992 --- /dev/null +++ b/pkg/golinters/gosimple/testdata/fix/out/gosimple.go @@ -0,0 +1,8 @@ +//golangcitest:args -Egosimple +//golangcitest:expected_exitcode 0 +package testdata + +func _(src []string) { + var dst []string + copy(dst, src) +} From 0cea44bb2ef9222aa255b1801106243a836266e3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:38:54 +0100 Subject: [PATCH 38/57] tests: autofix stylecheck --- pkg/golinters/stylecheck/stylecheck_integration_test.go | 8 ++++++++ pkg/golinters/stylecheck/testdata/fix/in/stylecheck.go | 9 +++++++++ pkg/golinters/stylecheck/testdata/fix/out/stylecheck.go | 9 +++++++++ 3 files changed, 26 insertions(+) create mode 100644 pkg/golinters/stylecheck/testdata/fix/in/stylecheck.go create mode 100644 pkg/golinters/stylecheck/testdata/fix/out/stylecheck.go diff --git a/pkg/golinters/stylecheck/stylecheck_integration_test.go b/pkg/golinters/stylecheck/stylecheck_integration_test.go index b82e5d3b13c9..2ee404e24b4b 100644 --- a/pkg/golinters/stylecheck/stylecheck_integration_test.go +++ b/pkg/golinters/stylecheck/stylecheck_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/stylecheck/testdata/fix/in/stylecheck.go b/pkg/golinters/stylecheck/testdata/fix/in/stylecheck.go new file mode 100644 index 000000000000..b20df7227cb8 --- /dev/null +++ b/pkg/golinters/stylecheck/testdata/fix/in/stylecheck.go @@ -0,0 +1,9 @@ +//golangcitest:args -Estylecheck +//golangcitest:expected_exitcode 0 +package testdata + +func _(x int) { + if 42 == x { + + } +} diff --git a/pkg/golinters/stylecheck/testdata/fix/out/stylecheck.go b/pkg/golinters/stylecheck/testdata/fix/out/stylecheck.go new file mode 100644 index 000000000000..28e83cff5d86 --- /dev/null +++ b/pkg/golinters/stylecheck/testdata/fix/out/stylecheck.go @@ -0,0 +1,9 @@ +//golangcitest:args -Estylecheck +//golangcitest:expected_exitcode 0 +package testdata + +func _(x int) { + if x == 42 { + + } +} From 6f36d379a019d48b3f5235759ee63619575fe3ac Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:39:11 +0100 Subject: [PATCH 39/57] tests: autofix staticcheck --- .../staticcheck/staticcheck_integration_test.go | 8 ++++++++ pkg/golinters/staticcheck/testdata/fix/in/staticcheck.go | 9 +++++++++ .../staticcheck/testdata/fix/out/staticcheck.go | 9 +++++++++ 3 files changed, 26 insertions(+) create mode 100644 pkg/golinters/staticcheck/testdata/fix/in/staticcheck.go create mode 100644 pkg/golinters/staticcheck/testdata/fix/out/staticcheck.go diff --git a/pkg/golinters/staticcheck/staticcheck_integration_test.go b/pkg/golinters/staticcheck/staticcheck_integration_test.go index ed09970659f9..f0d7fba8a84c 100644 --- a/pkg/golinters/staticcheck/staticcheck_integration_test.go +++ b/pkg/golinters/staticcheck/staticcheck_integration_test.go @@ -9,3 +9,11 @@ import ( func TestFromTestdata(t *testing.T) { integration.RunTestdata(t) } + +func TestFix(t *testing.T) { + integration.RunFix(t) +} + +func TestFixPathPrefix(t *testing.T) { + integration.RunFixPathPrefix(t) +} diff --git a/pkg/golinters/staticcheck/testdata/fix/in/staticcheck.go b/pkg/golinters/staticcheck/testdata/fix/in/staticcheck.go new file mode 100644 index 000000000000..68c84c6c7ca5 --- /dev/null +++ b/pkg/golinters/staticcheck/testdata/fix/in/staticcheck.go @@ -0,0 +1,9 @@ +//golangcitest:args -Estaticcheck +//golangcitest:expected_exitcode 0 +package testdata + +import "sort" + +func _(a []string) { + a = sort.StringSlice(a) +} diff --git a/pkg/golinters/staticcheck/testdata/fix/out/staticcheck.go b/pkg/golinters/staticcheck/testdata/fix/out/staticcheck.go new file mode 100644 index 000000000000..37f1cb4d772e --- /dev/null +++ b/pkg/golinters/staticcheck/testdata/fix/out/staticcheck.go @@ -0,0 +1,9 @@ +//golangcitest:args -Estaticcheck +//golangcitest:expected_exitcode 0 +package testdata + +import "sort" + +func _(a []string) { + sort.Strings(a) +} From 0dbb927804ac6e0e36d196ea56bd0900482af2ee Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:39:29 +0100 Subject: [PATCH 40/57] tests: autofix whitespace --- .../whitespace/testdata/fix/in/whitespace.go | 1 - .../whitespace/testdata/fix/out/whitespace.go | 6 +- .../whitespace/testdata/whitespace.go | 82 +++++++++++++++++++ .../whitespace/testdata/whitespace.yml | 4 + 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 pkg/golinters/whitespace/testdata/whitespace.go create mode 100644 pkg/golinters/whitespace/testdata/whitespace.yml diff --git a/pkg/golinters/whitespace/testdata/fix/in/whitespace.go b/pkg/golinters/whitespace/testdata/fix/in/whitespace.go index b10ca1718833..f6540de76e98 100644 --- a/pkg/golinters/whitespace/testdata/fix/in/whitespace.go +++ b/pkg/golinters/whitespace/testdata/fix/in/whitespace.go @@ -5,7 +5,6 @@ package p import "fmt" -//line yaccpar:1 func oneLeadingNewline() { fmt.Println("Hello world") diff --git a/pkg/golinters/whitespace/testdata/fix/out/whitespace.go b/pkg/golinters/whitespace/testdata/fix/out/whitespace.go index a29fc4125fc7..504fb57934f4 100644 --- a/pkg/golinters/whitespace/testdata/fix/out/whitespace.go +++ b/pkg/golinters/whitespace/testdata/fix/out/whitespace.go @@ -5,7 +5,6 @@ package p import "fmt" -//line yaccpar:1 func oneLeadingNewline() { fmt.Println("Hello world") } @@ -23,7 +22,6 @@ func oneNewlineFunc() { func twoNewlinesFunc() { - } func noNewlineWithCommentFunc() { @@ -63,7 +61,7 @@ func multiIfFunc() { if 1 == 1 && 2 == 2 { - fmt.Println("Hello nested multi-line world") + fmt.Println("Hello nested multi-line world") } } } @@ -71,5 +69,5 @@ func multiIfFunc() { } func notGoFmted() { - fmt.Println("Hello world") + fmt.Println("Hello world") } diff --git a/pkg/golinters/whitespace/testdata/whitespace.go b/pkg/golinters/whitespace/testdata/whitespace.go new file mode 100644 index 000000000000..1c9da1edd6c9 --- /dev/null +++ b/pkg/golinters/whitespace/testdata/whitespace.go @@ -0,0 +1,82 @@ +//golangcitest:args -Ewhitespace +//golangcitest:config_path testdata/whitespace.yml +package testdata + +import "fmt" + +func oneLeadingNewline() { + + fmt.Println("Hello world") +} + +func oneNewlineAtBothEnds() { + + fmt.Println("Hello world") + +} + +func noNewlineFunc() { +} + +func oneNewlineFunc() { + +} + +func twoNewlinesFunc() { + + +} + +func noNewlineWithCommentFunc() { + // some comment +} + +func oneTrailingNewlineWithCommentFunc() { + // some comment + +} + +func oneLeadingNewlineWithCommentFunc() { + + // some comment +} + +func twoLeadingNewlines() { + + + fmt.Println("Hello world") +} + +func multiFuncFunc(a int, + b int) { + fmt.Println("Hello world") +} + +func multiIfFunc() { + if 1 == 1 && + 2 == 2 { + fmt.Println("Hello multi-line world") + } + + if true { + if true { + if true { + if 1 == 1 && + 2 == 2 { + fmt.Println("Hello nested multi-line world") + } + } + } + } +} + +func notGoFmted() { + + + + + fmt.Println("Hello world") + + + +} diff --git a/pkg/golinters/whitespace/testdata/whitespace.yml b/pkg/golinters/whitespace/testdata/whitespace.yml new file mode 100644 index 000000000000..dad470cee82d --- /dev/null +++ b/pkg/golinters/whitespace/testdata/whitespace.yml @@ -0,0 +1,4 @@ +linters-settings: + whitespace: + multi-if: true + multi-func: true From c4ba66d9c200b7e7995d21b3ca519af39f6322bc Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:40:28 +0100 Subject: [PATCH 41/57] tests: multiple fixes --- test/testdata/fix/in/multiple-issues-fix.go | 3 ++- test/testdata/fix/out/multiple-issues-fix.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/testdata/fix/in/multiple-issues-fix.go b/test/testdata/fix/in/multiple-issues-fix.go index af56df5227c0..c781bed23d1b 100644 --- a/test/testdata/fix/in/multiple-issues-fix.go +++ b/test/testdata/fix/in/multiple-issues-fix.go @@ -1,4 +1,4 @@ -//golangcitest:args -Egocritic,gofumpt +//golangcitest:args -Egofumpt,misspell //golangcitest:config_path testdata/configs/multiple-issues-fix.yml //golangcitest:expected_exitcode 0 package p @@ -8,4 +8,5 @@ import "fmt" func main() { //standard greeting fmt.Println("hello world") + // langauge } diff --git a/test/testdata/fix/out/multiple-issues-fix.go b/test/testdata/fix/out/multiple-issues-fix.go index 524fb94140e8..41b75cd5f196 100644 --- a/test/testdata/fix/out/multiple-issues-fix.go +++ b/test/testdata/fix/out/multiple-issues-fix.go @@ -1,4 +1,4 @@ -//golangcitest:args -Egocritic,gofumpt +//golangcitest:args -Egofumpt,misspell //golangcitest:config_path testdata/configs/multiple-issues-fix.yml //golangcitest:expected_exitcode 0 package p @@ -8,4 +8,5 @@ import "fmt" func main() { // standard greeting fmt.Println("hello world") + // language } From 3c47cda5123007d6758afda717778cc8d48732e0 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:41:24 +0100 Subject: [PATCH 42/57] chore: migrate LLL --- pkg/golinters/lll/lll.go | 98 +++++++------------ pkg/golinters/lll/testdata/lll.go | 3 +- .../lll/testdata/lll_max_scan_token_size.go | 6 ++ .../testdata/lll_max_scan_token_size_cgo.go | 26 +++++ test/run_test.go | 2 +- 5 files changed, 70 insertions(+), 65 deletions(-) create mode 100644 pkg/golinters/lll/testdata/lll_max_scan_token_size.go create mode 100644 pkg/golinters/lll/testdata/lll_max_scan_token_size_cgo.go diff --git a/pkg/golinters/lll/lll.go b/pkg/golinters/lll/lll.go index 67f89eecbdd6..b49a805b3fd6 100644 --- a/pkg/golinters/lll/lll.go +++ b/pkg/golinters/lll/lll.go @@ -4,19 +4,15 @@ import ( "bufio" "errors" "fmt" - "go/token" + "go/ast" "os" "strings" - "sync" "unicode/utf8" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "lll" @@ -24,26 +20,15 @@ const linterName = "lll" const goCommentDirectivePrefix = "//go:" func New(settings *config.LllSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runLll(pass, settings) + err := runLll(pass, settings) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil }, } @@ -53,40 +38,35 @@ func New(settings *config.LllSettings) *goanalysis.Linter { "Reports long lines", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runLll(pass *analysis.Pass, settings *config.LllSettings) ([]goanalysis.Issue, error) { - fileNames := internal.GetFileNames(pass) - +func runLll(pass *analysis.Pass, settings *config.LllSettings) error { spaces := strings.Repeat(" ", settings.TabWidth) - var issues []goanalysis.Issue - for _, f := range fileNames { - lintIssues, err := getLLLIssuesForFile(f, settings.LineLength, spaces) + for _, file := range pass.Files { + err := getLLLIssuesForFile(pass, file, settings.LineLength, spaces) if err != nil { - return nil, err - } - - for i := range lintIssues { - issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass)) + return err } } - return issues, nil + return nil } -func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]result.Issue, error) { - var res []result.Issue +func getLLLIssuesForFile(pass *analysis.Pass, file *ast.File, maxLineLen int, tabSpaces string) error { + position := goanalysis.GetFilePosition(pass, file) + nonAdjPosition := pass.Fset.PositionFor(file.Pos(), false) - f, err := os.Open(filename) + f, err := os.Open(position.Filename) if err != nil { - return nil, fmt.Errorf("can't open file %s: %w", filename, err) + return fmt.Errorf("can't open file %s: %w", position.Filename, err) } + defer f.Close() + ft := pass.Fset.File(file.Pos()) + lineNumber := 0 multiImportEnabled := false @@ -116,42 +96,34 @@ func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]r lineLen := utf8.RuneCountInString(line) if lineLen > maxLineLen { - res = append(res, result.Issue{ - Pos: token.Position{ - Filename: filename, - Line: lineNumber, - }, - Text: fmt.Sprintf("the line is %d characters long, which exceeds the maximum of %d characters.", lineLen, maxLineLen), - FromLinter: linterName, + pass.Report(analysis.Diagnostic{ + Pos: ft.LineStart(goanalysis.AdjustPos(lineNumber, nonAdjPosition.Line, position.Line)), + Message: fmt.Sprintf("The line is %d characters long, which exceeds the maximum of %d characters.", + lineLen, maxLineLen), }) } } if err := scanner.Err(); err != nil { + // scanner.Scan() might fail if the line is longer than bufio.MaxScanTokenSize + // In the case where the specified maxLineLen is smaller than bufio.MaxScanTokenSize + // we can return this line as a long line instead of returning an error. + // The reason for this change is that this case might happen with autogenerated files + // The go-bindata tool for instance might generate a file with a very long line. + // In this case, as it's an auto generated file, the warning returned by lll will + // be ignored. + // But if we return a linter error here, and this error happens for an autogenerated + // file the error will be discarded (fine), but all the subsequent errors for lll will + // be discarded for other files, and we'll miss legit error. if errors.Is(err, bufio.ErrTooLong) && maxLineLen < bufio.MaxScanTokenSize { - // scanner.Scan() might fail if the line is longer than bufio.MaxScanTokenSize - // In the case where the specified maxLineLen is smaller than bufio.MaxScanTokenSize - // we can return this line as a long line instead of returning an error. - // The reason for this change is that this case might happen with autogenerated files - // The go-bindata tool for instance might generate a file with a very long line. - // In this case, as it's an auto generated file, the warning returned by lll will - // be ignored. - // But if we return a linter error here, and this error happens for an autogenerated - // file the error will be discarded (fine), but all the subsequent errors for lll will - // be discarded for other files, and we'll miss legit error. - res = append(res, result.Issue{ - Pos: token.Position{ - Filename: filename, - Line: lineNumber, - Column: 1, - }, - Text: fmt.Sprintf("line is more than %d characters", bufio.MaxScanTokenSize), - FromLinter: linterName, + pass.Report(analysis.Diagnostic{ + Pos: ft.LineStart(goanalysis.AdjustPos(lineNumber, nonAdjPosition.Line, position.Line)), + Message: fmt.Sprintf("line is more than %d characters", bufio.MaxScanTokenSize), }) } else { - return nil, fmt.Errorf("can't scan file %s: %w", filename, err) + return fmt.Errorf("can't scan file %s: %w", position.Filename, err) } } - return res, nil + return nil } diff --git a/pkg/golinters/lll/testdata/lll.go b/pkg/golinters/lll/testdata/lll.go index 729a6cc28809..d0f7ed57c560 100644 --- a/pkg/golinters/lll/testdata/lll.go +++ b/pkg/golinters/lll/testdata/lll.go @@ -7,7 +7,8 @@ import ( ) func Lll() { - // In my experience, long lines are the lines with comments, not the code. So this is a long comment // want "line is 137 characters" + // want +1 "line is 141 characters" + // In my experience, long lines are the lines with comments, not the code. So this is a long comment, a very long comment, yes very long. } //go:generate mockgen -source lll.go -destination a_verylong_generate_mock_my_lll_interface.go --package testdata -self_package github.com/golangci/golangci-lint/test/testdata diff --git a/pkg/golinters/lll/testdata/lll_max_scan_token_size.go b/pkg/golinters/lll/testdata/lll_max_scan_token_size.go new file mode 100644 index 000000000000..ca7fd8a5dcda --- /dev/null +++ b/pkg/golinters/lll/testdata/lll_max_scan_token_size.go @@ -0,0 +1,6 @@ +//golangcitest:args -Elll +//golangcitest:config_path testdata/lll.yml +package testdata + +// want "line is more than 65536 characters" +// Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus. diff --git a/pkg/golinters/lll/testdata/lll_max_scan_token_size_cgo.go b/pkg/golinters/lll/testdata/lll_max_scan_token_size_cgo.go new file mode 100644 index 000000000000..739ebedc4507 --- /dev/null +++ b/pkg/golinters/lll/testdata/lll_max_scan_token_size_cgo.go @@ -0,0 +1,26 @@ +//golangcitest:args -Elll +//golangcitest:config_path testdata/lll.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// want "line is more than 65536 characters" +// Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus vestibulum. Fusce commodo aliquam arcu. Nam commodo suscipit quam. Quisque id odio. Praesent venenatis metus at tortor pulvinar varius. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In dui magna, posuere eget, vestibulum et, tempor auctor, justo. In ac felis quis tortor malesuada pretium. Pellentesque auctor neque nec urna. Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Aenean viverra rhoncus pede. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut non enim eleifend felis pretium feugiat. Vivamus quis mi. Phasellus a est. Phasellus magna. In hac habitasse platea dictumst. Curabitur at lacus ac velit ornare lobortis. Curabitur a felis in nunc fringilla tristique. Morbi mattis ullamcorper velit. Phasellus gravida semper nisi. Nullam vel sem. Pellentesque libero tortor, tincidunt et, tincidunt eget, semper nec, quam. Sed hendrerit. Morbi ac felis. Nunc egestas, augue at pellentesque laoreet, felis eros vehicula leo, at malesuada velit leo quis pede. Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Nunc nulla. Fusce risus nisl, viverra et, tempor et, pretium in, sapien. Donec venenatis vulputate lorem. Morbi nec metus. Phasellus blandit leo ut odio. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Sed magna purus, fermentum eu, tincidunt eu, varius ut, felis. In auctor lobortis lacus. Quisque libero metus, condimentum nec, tempor a, commodo mollis, magna. Vestibulum ullamcorper mauris at ligula. Fusce fermentum. Nullam cursus lacinia erat. Praesent blandit laoreet nibh. Fusce convallis metus id felis luctus adipiscing. Pellentesque egestas, neque sit amet convallis pulvinar, justo nulla eleifend augue, ac auctor orci leo non est. Quisque id mi. Ut tincidunt tincidunt erat. Etiam feugiat lorem non metus. Vestibulum dapibus nunc ac augue. Curabitur vestibulum aliquam leo. Praesent egestas neque eu enim. In hac habitasse platea dictumst. Fusce a quam. Etiam ut purus mattis mauris sodales aliquam. Curabitur nisi. Quisque malesuada placerat nisl. Nam ipsum risus, rutrum vitae, vestibulum eu, molestie vel, lacus. Sed augue ipsum, egestas nec, vestibulum et, malesuada adipiscing, dui. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Mauris sollicitudin fermentum libero. Praesent nonummy mi in odio. Nunc interdum lacus sit amet orci. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Morbi mollis tellus ac sapien. Phasellus volutpat, metus eget egestas mollis, lacus lacus blandit dui, id egestas quam mauris ut lacus. Fusce vel dui. Sed in libero ut nibh placerat accumsan. Proin faucibus arcu quis ante. In consectetuer turpis ut velit. Nulla sit amet est. Praesent metus tellus, elementum eu, semper a, adipiscing nec, purus. Cras risus ipsum, faucibus ut, ullamcorper id, varius ac, leo. Suspendisse feugiat. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Praesent nec nisl a purus blandit viverra. Praesent ac massa at ligula laoreet iaculis. Nulla neque dolor, sagittis eget, iaculis quis, molestie non, velit. Mauris turpis nunc, blandit et, volutpat molestie, porta ut, ligula. Fusce pharetra convallis urna. Quisque ut nisi. Donec mi odio, faucibus at, scelerisque quis, convallis in, nisi. Suspendisse non nisl sit amet velit hendrerit rutrum. Ut leo. Ut a nisl id ante tempus hendrerit. Proin pretium, leo ac pellentesque mollis, felis nunc ultrices eros, sed gravida augue augue mollis justo. Suspendisse eu ligula. Nulla facilisi. Donec id justo. Praesent porttitor, nulla vitae posuere iaculis, arcu nisl dignissim dolor, a pretium mi sem ut ipsum. Curabitur suscipit suscipit tellus. Praesent vestibulum dapibus nibh. Etiam iaculis nunc ac metus. Ut id nisl quis enim dignissim sagittis. Etiam sollicitudin, ipsum eu pulvinar rutrum, tellus ipsum laoreet sapien, quis venenatis ante odio sit amet eros. Proin magna. Duis vel nibh at velit scelerisque suscipit. Curabitur turpis. Vestibulum suscipit nulla quis orci. Fusce ac felis sit amet ligula pharetra condimentum. Maecenas egestas arcu quis ligula mattis placerat. Duis lobortis massa imperdiet quam. Suspendisse potenti. Pellentesque commodo eros a enim. Vestibulum turpis sem, aliquet eget, lobortis pellentesque, rutrum eu, nisl. Sed libero. Aliquam erat volutpat. Etiam vitae tortor. Morbi vestibulum volutpat enim. Aliquam eu nunc. Nunc sed turpis. Sed mollis, eros et ultrices tempus, mauris ipsum aliquam libero, non adipiscing dolor urna a orci. Nulla porta dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque dapibus hendrerit tortor. Praesent egestas tristique nibh. Sed a libero. Cras varius. Donec vitae orci sed dolor rutrum auctor. Fusce egestas elit eget lorem. Suspendisse nisl elit, rhoncus eget, elementum ac, condimentum eget, diam. Nam at tortor in tellus interdum sagittis. Aliquam lobortis. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Curabitur blandit mollis lacus. Nam adipiscing. Vestibulum eu odio. Vivamus laoreet. Nullam tincidunt adipiscing enim. Phasellus tempus. Proin viverra, ligula sit amet ultrices semper, ligula arcu tristique sapien, a accumsan nisi mauris ac eros. Fusce neque. Suspendisse faucibus, nunc et pellentesque egestas, lacus ante convallis tellus, vitae iaculis lacus elit id tortor. Vivamus aliquet elit ac nisl. Fusce fermentum odio nec arcu. Vivamus euismod mauris. In ut quam vitae odio lacinia tincidunt. Praesent ut ligula non mi varius sagittis. Cras sagittis. Praesent ac sem eget est egestas volutpat. Vivamus consectetuer hendrerit lacus. Cras non dolor. Vivamus in erat ut urna cursus. diff --git a/test/run_test.go b/test/run_test.go index 4c05402a3be5..883e1e5df79a 100644 --- a/test/run_test.go +++ b/test/run_test.go @@ -216,7 +216,7 @@ func TestLineDirective(t *testing.T) { }, configPath: "testdata/linedirective/lll.yml", targetPath: "linedirective", - expected: "the line is 57 characters long, which exceeds the maximum of 50 characters. (lll)", + expected: "The line is 57 characters long, which exceeds the maximum of 50 characters. (lll)", }, { desc: "misspell", From 66c31e611c4b446c6f1df7fab7859c7c1c506419 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:47:02 +0100 Subject: [PATCH 43/57] chore: simplify godot --- pkg/golinters/godot/godot.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/pkg/golinters/godot/godot.go b/pkg/golinters/godot/godot.go index 2d8135393de7..2ec7bdab67d7 100644 --- a/pkg/golinters/godot/godot.go +++ b/pkg/golinters/godot/godot.go @@ -2,7 +2,6 @@ package godot import ( "cmp" - "go/token" "github.com/tetafro/godot" "golang.org/x/tools/go/analysis" @@ -60,26 +59,23 @@ func runGodot(pass *analysis.Pass, settings godot.Settings) error { return err } - for _, i := range iss { - f := pass.Fset.File(file.Pos()) + if len(iss) == 0 { + continue + } - pos := f.Pos(i.Pos.Offset) + f := pass.Fset.File(file.Pos()) - var end token.Pos - if i.Pos.Line == f.LineCount() { - // missing newline at the end of the file - end = f.Pos(f.Size()) - } else { - end = f.LineStart(i.Pos.Line+1) - token.Pos(1) - } + for _, i := range iss { + start := f.Pos(i.Pos.Offset) + end := goanalysis.EndOfLinePos(f, i.Pos.Line) pass.Report(analysis.Diagnostic{ - Pos: pos, + Pos: start, End: end, Message: i.Message, SuggestedFixes: []analysis.SuggestedFix{{ TextEdits: []analysis.TextEdit{{ - Pos: pos, + Pos: start, End: end, NewText: []byte(i.Replacement), }}, From b8d5d41999d688a6cda6bcf422b8dd2cce9b8ff0 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:51:48 +0100 Subject: [PATCH 44/57] chore: migrate forbidigo --- pkg/golinters/forbidigo/forbidigo.go | 47 +++++++++++----------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/pkg/golinters/forbidigo/forbidigo.go b/pkg/golinters/forbidigo/forbidigo.go index 3572b60c23cb..dececf350ad3 100644 --- a/pkg/golinters/forbidigo/forbidigo.go +++ b/pkg/golinters/forbidigo/forbidigo.go @@ -2,40 +2,27 @@ package forbidigo import ( "fmt" - "sync" "github.com/ashanbrown/forbidigo/forbidigo" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "forbidigo" func New(settings *config.ForbidigoSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runForbidigo(pass, settings) + err := runForbidigo(pass, settings) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() return nil, nil }, } @@ -48,12 +35,10 @@ func New(settings *config.ForbidigoSettings) *goanalysis.Linter { "Forbids identifiers", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func runForbidigo(pass *analysis.Pass, settings *config.ForbidigoSettings) ([]goanalysis.Issue, error) { +func runForbidigo(pass *analysis.Pass, settings *config.ForbidigoSettings) error { options := []forbidigo.Option{ forbidigo.OptionExcludeGodocExamples(settings.ExcludeGodocExamples), // disable "//permit" directives so only "//nolint" directives matters within golangci-lint @@ -66,38 +51,42 @@ func runForbidigo(pass *analysis.Pass, settings *config.ForbidigoSettings) ([]go for _, pattern := range settings.Forbid { buffer, err := pattern.MarshalString() if err != nil { - return nil, err + return err } + patterns = append(patterns, string(buffer)) } forbid, err := forbidigo.NewLinter(patterns, options...) if err != nil { - return nil, fmt.Errorf("failed to create linter %q: %w", linterName, err) + return fmt.Errorf("failed to create linter %q: %w", linterName, err) } - var issues []goanalysis.Issue for _, file := range pass.Files { runConfig := forbidigo.RunConfig{ Fset: pass.Fset, DebugLog: logutils.Debug(logutils.DebugKeyForbidigo), } - if settings != nil && settings.AnalyzeTypes { + + if settings.AnalyzeTypes { runConfig.TypesInfo = pass.TypesInfo } + hints, err := forbid.RunWithConfig(runConfig, file) if err != nil { - return nil, fmt.Errorf("forbidigo linter failed on file %q: %w", file.Name.String(), err) + return fmt.Errorf("forbidigo linter failed on file %q: %w", file.Name.String(), err) } for _, hint := range hints { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: hint.Position(), - Text: hint.Details(), - FromLinter: linterName, - }, pass)) + pass.Report(analysis.Diagnostic{ + Pos: hint.Pos(), + Message: hint.Details(), + URL: "", + SuggestedFixes: nil, + Related: nil, + }) } } - return issues, nil + return nil } From 30af54f6665886af45463838369b842b915fa2d5 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:52:37 +0100 Subject: [PATCH 45/57] chore: migrate godox --- pkg/golinters/godox/godox.go | 54 ++++++++++++------------------------ 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/pkg/golinters/godox/godox.go b/pkg/golinters/godox/godox.go index d8de026baf10..4464e1d8ec5a 100644 --- a/pkg/golinters/godox/godox.go +++ b/pkg/golinters/godox/godox.go @@ -3,36 +3,22 @@ package godox import ( "go/token" "strings" - "sync" "github.com/matoous/godox" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "godox" func New(settings *config.GodoxSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues := runGodox(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() + runGodox(pass, settings) return nil, nil }, @@ -43,33 +29,27 @@ func New(settings *config.GodoxSettings) *goanalysis.Linter { "Tool for detection of FIXME, TODO and other comment keywords", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runGodox(pass *analysis.Pass, settings *config.GodoxSettings) []goanalysis.Issue { - var messages []godox.Message +func runGodox(pass *analysis.Pass, settings *config.GodoxSettings) { for _, file := range pass.Files { - messages = append(messages, godox.Run(file, pass.Fset, settings.Keywords...)...) - } + position := goanalysis.GetFilePosition(pass, file) - if len(messages) == 0 { - return nil - } + messages := godox.Run(file, pass.Fset, settings.Keywords...) + if len(messages) == 0 { + continue + } - issues := make([]goanalysis.Issue, len(messages)) + nonAdjPosition := pass.Fset.PositionFor(file.Pos(), false) - for k, i := range messages { - issues[k] = goanalysis.NewIssue(&result.Issue{ - Pos: token.Position{ - Filename: i.Pos.Filename, - Line: i.Pos.Line, - }, - Text: strings.TrimRight(i.Message, "\n"), - FromLinter: linterName, - }, pass) - } + ft := pass.Fset.File(file.Pos()) - return issues + for _, i := range messages { + pass.Report(analysis.Diagnostic{ + Pos: ft.LineStart(goanalysis.AdjustPos(i.Pos.Line, nonAdjPosition.Line, position.Line)) + token.Pos(i.Pos.Column), + Message: strings.TrimRight(i.Message, "\n"), + }) + } + } } From 7d46339481eba2064af64be938161655645389a8 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:53:03 +0100 Subject: [PATCH 46/57] chore: migrate makezero --- pkg/golinters/makezero/makezero.go | 37 ++++++++---------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/pkg/golinters/makezero/makezero.go b/pkg/golinters/makezero/makezero.go index ae4bf2184222..b5ab4515e546 100644 --- a/pkg/golinters/makezero/makezero.go +++ b/pkg/golinters/makezero/makezero.go @@ -2,40 +2,26 @@ package makezero import ( "fmt" - "sync" "github.com/ashanbrown/makezero/makezero" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "makezero" func New(settings *config.MakezeroSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runMakeZero(pass, settings) + err := runMakeZero(pass, settings) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil }, } @@ -45,30 +31,25 @@ func New(settings *config.MakezeroSettings) *goanalysis.Linter { "Finds slice declarations with non-zero initial length", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeTypesInfo) + ).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func runMakeZero(pass *analysis.Pass, settings *config.MakezeroSettings) ([]goanalysis.Issue, error) { +func runMakeZero(pass *analysis.Pass, settings *config.MakezeroSettings) error { zero := makezero.NewLinter(settings.Always) - var issues []goanalysis.Issue - for _, file := range pass.Files { hints, err := zero.Run(pass.Fset, pass.TypesInfo, file) if err != nil { - return nil, fmt.Errorf("makezero linter failed on file %q: %w", file.Name.String(), err) + return fmt.Errorf("makezero linter failed on file %q: %w", file.Name.String(), err) } for _, hint := range hints { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: hint.Position(), - Text: hint.Details(), - FromLinter: linterName, - }, pass)) + pass.Report(analysis.Diagnostic{ + Pos: hint.Pos(), + Message: hint.Details(), + }) } } - return issues, nil + return nil } From 600e10e7f4ed494711f01c425b2b738eade9c028 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:53:14 +0100 Subject: [PATCH 47/57] chore: migrate nestif --- pkg/golinters/nestif/nestif.go | 60 +++++++++++----------------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/pkg/golinters/nestif/nestif.go b/pkg/golinters/nestif/nestif.go index 43be973b0aee..924f14e5fac3 100644 --- a/pkg/golinters/nestif/nestif.go +++ b/pkg/golinters/nestif/nestif.go @@ -1,37 +1,21 @@ package nestif import ( - "sort" - "sync" - "github.com/nakabonne/nestif" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "nestif" func New(settings *config.NestifSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ - Name: goanalysis.TheOnlyAnalyzerName, + Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues := runNestIf(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() + runNestIf(pass, settings) return nil, nil }, @@ -42,37 +26,31 @@ func New(settings *config.NestifSettings) *goanalysis.Linter { "Reports deeply nested if statements", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runNestIf(pass *analysis.Pass, settings *config.NestifSettings) []goanalysis.Issue { +func runNestIf(pass *analysis.Pass, settings *config.NestifSettings) { checker := &nestif.Checker{ MinComplexity: settings.MinComplexity, } - var lintIssues []nestif.Issue - for _, f := range pass.Files { - lintIssues = append(lintIssues, checker.Check(f, pass.Fset)...) - } + for _, file := range pass.Files { + position := goanalysis.GetFilePosition(pass, file) - if len(lintIssues) == 0 { - return nil - } + issues := checker.Check(file, pass.Fset) + if len(issues) == 0 { + continue + } - sort.SliceStable(lintIssues, func(i, j int) bool { - return lintIssues[i].Complexity > lintIssues[j].Complexity - }) + nonAdjPosition := pass.Fset.PositionFor(file.Pos(), false) - issues := make([]goanalysis.Issue, 0, len(lintIssues)) - for _, i := range lintIssues { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: i.Pos, - Text: i.Message, - FromLinter: linterName, - }, pass)) - } + f := pass.Fset.File(file.Pos()) - return issues + for _, issue := range issues { + pass.Report(analysis.Diagnostic{ + Pos: f.LineStart(goanalysis.AdjustPos(issue.Pos.Line, nonAdjPosition.Line, position.Line)), + Message: issue.Message, + }) + } + } } From c7e3c0f6da5b6b99753ba10c629129233ab63820 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:53:27 +0100 Subject: [PATCH 48/57] chore: migrate prealloc --- pkg/golinters/prealloc/prealloc.go | 35 ++++++------------------------ 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/pkg/golinters/prealloc/prealloc.go b/pkg/golinters/prealloc/prealloc.go index ce7ff9d59cb5..17e86c98ee0a 100644 --- a/pkg/golinters/prealloc/prealloc.go +++ b/pkg/golinters/prealloc/prealloc.go @@ -2,7 +2,6 @@ package prealloc import ( "fmt" - "sync" "github.com/alexkohler/prealloc/pkg" "golang.org/x/tools/go/analysis" @@ -10,29 +9,16 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" "github.com/golangci/golangci-lint/pkg/golinters/internal" - "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "prealloc" func New(settings *config.PreallocSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Run: func(pass *analysis.Pass) (any, error) { - issues := runPreAlloc(pass, settings) - - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() + runPreAlloc(pass, settings) return nil, nil }, @@ -43,23 +29,16 @@ func New(settings *config.PreallocSettings) *goanalysis.Linter { "Finds slice declarations that could potentially be pre-allocated", []*analysis.Analyzer{analyzer}, nil, - ).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues - }).WithLoadMode(goanalysis.LoadModeSyntax) + ).WithLoadMode(goanalysis.LoadModeSyntax) } -func runPreAlloc(pass *analysis.Pass, settings *config.PreallocSettings) []goanalysis.Issue { - var issues []goanalysis.Issue - +func runPreAlloc(pass *analysis.Pass, settings *config.PreallocSettings) { hints := pkg.Check(pass.Files, settings.Simple, settings.RangeLoops, settings.ForLoops) for _, hint := range hints { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: pass.Fset.Position(hint.Pos), - Text: fmt.Sprintf("Consider pre-allocating %s", internal.FormatCode(hint.DeclaredSliceName, nil)), - FromLinter: linterName, - }, pass)) + pass.Report(analysis.Diagnostic{ + Pos: hint.Pos, + Message: fmt.Sprintf("Consider pre-allocating %s", internal.FormatCode(hint.DeclaredSliceName, nil)), + }) } - - return issues } From e1fbf358e8160b0d897cdcd72562f8d728e294b5 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 01:53:56 +0100 Subject: [PATCH 49/57] chore: migrate unparam --- pkg/golinters/unparam/unparam.go | 34 ++++++++------------------------ 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/pkg/golinters/unparam/unparam.go b/pkg/golinters/unparam/unparam.go index 0fe184736629..04c9a223e512 100644 --- a/pkg/golinters/unparam/unparam.go +++ b/pkg/golinters/unparam/unparam.go @@ -1,8 +1,6 @@ package unparam import ( - "sync" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/packages" @@ -11,33 +9,21 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" - "github.com/golangci/golangci-lint/pkg/result" ) const linterName = "unparam" func New(settings *config.UnparamSettings) *goanalysis.Linter { - var mu sync.Mutex - var resIssues []goanalysis.Issue - analyzer := &analysis.Analyzer{ Name: linterName, Doc: goanalysis.TheOnlyanalyzerDoc, Requires: []*analysis.Analyzer{buildssa.Analyzer}, Run: func(pass *analysis.Pass) (any, error) { - issues, err := runUnparam(pass, settings) + err := runUnparam(pass, settings) if err != nil { return nil, err } - if len(issues) == 0 { - return nil, nil - } - - mu.Lock() - resIssues = append(resIssues, issues...) - mu.Unlock() - return nil, nil }, } @@ -51,12 +37,10 @@ func New(settings *config.UnparamSettings) *goanalysis.Linter { if settings.Algo != "cha" { lintCtx.Log.Warnf("`linters-settings.unparam.algo` isn't supported by the newest `unparam`") } - }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { - return resIssues }).WithLoadMode(goanalysis.LoadModeTypesInfo) } -func runUnparam(pass *analysis.Pass, settings *config.UnparamSettings) ([]goanalysis.Issue, error) { +func runUnparam(pass *analysis.Pass, settings *config.UnparamSettings) error { ssa := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA) ssaPkg := ssa.Pkg @@ -74,17 +58,15 @@ func runUnparam(pass *analysis.Pass, settings *config.UnparamSettings) ([]goanal unparamIssues, err := c.Check() if err != nil { - return nil, err + return err } - var issues []goanalysis.Issue for _, i := range unparamIssues { - issues = append(issues, goanalysis.NewIssue(&result.Issue{ - Pos: pass.Fset.Position(i.Pos()), - Text: i.Message(), - FromLinter: linterName, - }, pass)) + pass.Report(analysis.Diagnostic{ + Pos: i.Pos(), + Message: i.Message(), + }) } - return issues, nil + return nil } From fe88914e9853fb32dc8aada2a4ef93b343c34e98 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 02:07:18 +0100 Subject: [PATCH 50/57] tests: add cgo tests --- .../asasalint/testdata/asasalint_cgo.go | 39 ++++ .../asciicheck/testdata/asasalint_cgo.go | 40 ++++ pkg/golinters/bidichk/testdata/bidichk_cgo.go | 27 +++ .../bodyclose/testdata/bodyclose_cgo.go | 29 +++ .../testdata/canonicalheader_cgo.go | 40 ++++ .../containedctx/testdata/containedctx_cgo.go | 34 +++ .../contextcheck/testdata/contextcheck_cgo.go | 33 +++ .../copyloopvar/testdata/copyloopvar_cgo.go | 39 ++++ pkg/golinters/cyclop/testdata/cyclop_cgo.go | 35 +++ .../decorder/testdata/decorder_cgo.go | 40 ++++ .../depguard/testdata/depguard_cgo.go | 32 +++ pkg/golinters/dogsled/testdata/dogsled_cgo.go | 45 ++++ pkg/golinters/dupl/testdata/dupl_cgo.go | 54 +++++ pkg/golinters/dupword/testdata/dupword_cgo.go | 28 +++ .../testdata/durationcheck_cgo.go | 38 ++++ pkg/golinters/err113/testdata/err113_cgo.go | 32 +++ .../errcheck/testdata/errcheck_cgo.go | 30 +++ .../errchkjson/testdata/errchkjson_cgo.go | 33 +++ pkg/golinters/errname/testdata/errname_cgo.go | 35 +++ .../errorlint/testdata/errorlint_cgo.go | 48 ++++ .../exhaustive/testdata/exhaustive_cgo.go | 37 +++ .../exhaustruct/testdata/exhaustruct_cgo.go | 66 ++++++ .../testdata/exportloopref_cgo.go | 65 ++++++ .../fatcontext/testdata/fatcontext_cgo.go | 52 +++++ .../forbidigo/testdata/forbidigo_cgo.go | 32 +++ .../testdata/forcetypeassert_cgo.go | 39 ++++ pkg/golinters/funlen/testdata/funlen_cgo.go | 48 ++++ pkg/golinters/gci/testdata/gci_cgo.go | 36 +++ .../ginkgolinter/testdata/ginkgolinter_cgo.go | 41 ++++ .../testdata/gocheckcompilerdirectives_cgo.go | 40 ++++ .../testdata/gochecknoglobals_cgo.go | 37 +++ .../testdata/gochecknoinits_cgo.go | 29 +++ .../testdata/gochecksumtype_cgo.go | 61 +++++ .../gocognit/testdata/gocognit_cgo.go | 35 +++ pkg/golinters/goconst/testdata/goconst_cgo.go | 39 ++++ .../gocritic/testdata/gocritic_cgo.go | 26 +++ pkg/golinters/gocyclo/testdata/gocyclo_cgo.go | 38 ++++ pkg/golinters/godot/testdata/godot_cgo.go | 29 +++ pkg/golinters/godox/testdata/godox_cgo.go | 33 +++ pkg/golinters/gofmt/testdata/gofmt_cgo.go | 28 +++ pkg/golinters/gofumpt/testdata/gofumpt_cgo.go | 27 +++ .../goheader/testdata/goheader_cgo.go | 25 ++ .../goimports/testdata/goimports_cgo.go | 29 +++ .../gomodguard/testdata/gomodguard_cgo.go | 44 ++++ .../testdata/goprintffuncname_cgo.go | 25 ++ pkg/golinters/gosec/testdata/gosec_cgo.go | 29 +++ .../gosimple/testdata/gosimple_cgo.go | 31 +++ .../gosmopolitan/testdata/gosmopolitan_cgo.go | 42 ++++ pkg/golinters/govet/testdata/govet_cgo.go | 36 +++ pkg/golinters/grouper/testdata/grouper_cgo.go | 21 ++ pkg/golinters/iface/testdata/iface_cgo.go | 86 +++++++ .../importas/testdata/importas_cgo.go | 35 +++ .../inamedparam/testdata/inamedparam_cgo.go | 48 ++++ .../ineffassign/testdata/ineffassign_cgo.go | 32 +++ .../testdata/interfacebloat_cgo.go | 37 +++ .../intrange/testdata/intrange_cgo.go | 40 ++++ pkg/golinters/ireturn/testdata/ireturn_cgo.go | 32 +++ pkg/golinters/lll/testdata/lll_cgo.go | 26 +++ .../loggercheck/testdata/loggercheck_cgo.go | 53 +++++ .../maintidx/testdata/maintidx_cgo.go | 215 ++++++++++++++++++ .../makezero/testdata/makezero_cgo.go | 28 +++ pkg/golinters/mirror/testdata/mirror_cgo.go | 29 +++ .../misspell/testdata/misspell_cgo.go | 30 +++ pkg/golinters/mnd/testdata/mnd_cgo.go | 40 ++++ pkg/golinters/musttag/testdata/musttag_cgo.go | 42 ++++ .../nakedret/testdata/nakedret_cgo.go | 58 +++++ pkg/golinters/nestif/testdata/nestif_cgo.go | 70 ++++++ pkg/golinters/nilerr/testdata/nilerr_cgo.go | 36 +++ pkg/golinters/nilnil/testdata/nilnil_cgo.go | 26 +++ .../nlreturn/testdata/nlreturn_cgo.go | 57 +++++ pkg/golinters/noctx/testdata/noctx_cgo.go | 150 ++++++++++++ .../nolintlint/testdata/nolintlint_cgo.go | 38 ++++ .../testdata/nonamedreturns_cgo.go | 31 +++ .../testdata/nosprintfhostport_cgo.go | 27 +++ .../paralleltest/testdata/paralleltest_cgo.go | 25 ++ .../perfsprint/testdata/perfsprint_cgo.go | 70 ++++++ .../prealloc/testdata/prealloc_cgo.go | 31 +++ .../predeclared/testdata/predeclared_cgo.go | 33 +++ .../promlinter/testdata/promlinter_cgo.go | 52 +++++ .../protogetter/testdata/protogetter_cgo.go | 99 ++++++++ .../reassign/testdata/reassign_cgo.go | 30 +++ .../recvcheck/testdata/recvcheck_cgo.go | 33 +++ pkg/golinters/revive/testdata/revive_cgo.go | 47 ++++ .../rowserrcheck/testdata/rowserrcheck_cgo.go | 30 +++ .../sloglint/testdata/sloglint_cgo.go | 32 +++ .../spancheck/testdata/spancheck_cgo.go | 107 +++++++++ .../testdata/sqlclosecheck_cgo.go | 56 +++++ .../staticcheck/testdata/staticcheck_cgo.go | 28 +++ .../stylecheck/testdata/stylecheck_cgo.go | 33 +++ .../tagalign/testdata/tagalign_cgo.go | 34 +++ .../tagliatelle/testdata/tagliatelle_cgo.go | 52 +++++ .../testdata/testableexamples_test_cgo.go | 42 ++++ .../testifylint/testdata/testifylint_cgo.go | 81 +++++++ pkg/golinters/thelper/testdata/thelper_cgo.go | 28 +++ .../tparallel/testdata/tparallel_cgo.go | 30 +++ .../unconvert/testdata/unconvert_cgo.go | 136 +++++++++++ pkg/golinters/unparam/testdata/unparam_cgo.go | 27 +++ pkg/golinters/unused/testdata/unused_cgo.go | 24 ++ .../testdata/usestdlibvars_cgo.go | 32 +++ .../usetesting/testdata/usetesting_cgo.go | 28 +++ .../varnamelen/testdata/varnamelen_cgo.go | 36 +++ .../wastedassign/testdata/wastedassign_cgo.go | 185 +++++++++++++++ .../whitespace/testdata/whitespace_cgo.go | 102 +++++++++ .../wrapcheck/testdata/wrapcheck_cgo.go | 32 +++ pkg/golinters/wsl/testdata/wsl_cgo.go | 33 +++ .../zerologlint/testdata/zerologlint_cgo.go | 56 +++++ 106 files changed, 4781 insertions(+) create mode 100644 pkg/golinters/asasalint/testdata/asasalint_cgo.go create mode 100644 pkg/golinters/asciicheck/testdata/asasalint_cgo.go create mode 100644 pkg/golinters/bidichk/testdata/bidichk_cgo.go create mode 100644 pkg/golinters/bodyclose/testdata/bodyclose_cgo.go create mode 100644 pkg/golinters/canonicalheader/testdata/canonicalheader_cgo.go create mode 100644 pkg/golinters/containedctx/testdata/containedctx_cgo.go create mode 100644 pkg/golinters/contextcheck/testdata/contextcheck_cgo.go create mode 100644 pkg/golinters/copyloopvar/testdata/copyloopvar_cgo.go create mode 100644 pkg/golinters/cyclop/testdata/cyclop_cgo.go create mode 100644 pkg/golinters/decorder/testdata/decorder_cgo.go create mode 100644 pkg/golinters/depguard/testdata/depguard_cgo.go create mode 100644 pkg/golinters/dogsled/testdata/dogsled_cgo.go create mode 100644 pkg/golinters/dupl/testdata/dupl_cgo.go create mode 100644 pkg/golinters/dupword/testdata/dupword_cgo.go create mode 100644 pkg/golinters/durationcheck/testdata/durationcheck_cgo.go create mode 100644 pkg/golinters/err113/testdata/err113_cgo.go create mode 100644 pkg/golinters/errcheck/testdata/errcheck_cgo.go create mode 100644 pkg/golinters/errchkjson/testdata/errchkjson_cgo.go create mode 100644 pkg/golinters/errname/testdata/errname_cgo.go create mode 100644 pkg/golinters/errorlint/testdata/errorlint_cgo.go create mode 100644 pkg/golinters/exhaustive/testdata/exhaustive_cgo.go create mode 100644 pkg/golinters/exhaustruct/testdata/exhaustruct_cgo.go create mode 100644 pkg/golinters/exportloopref/testdata/exportloopref_cgo.go create mode 100644 pkg/golinters/fatcontext/testdata/fatcontext_cgo.go create mode 100644 pkg/golinters/forbidigo/testdata/forbidigo_cgo.go create mode 100644 pkg/golinters/forcetypeassert/testdata/forcetypeassert_cgo.go create mode 100644 pkg/golinters/funlen/testdata/funlen_cgo.go create mode 100644 pkg/golinters/gci/testdata/gci_cgo.go create mode 100644 pkg/golinters/ginkgolinter/testdata/ginkgolinter_cgo.go create mode 100644 pkg/golinters/gocheckcompilerdirectives/testdata/gocheckcompilerdirectives_cgo.go create mode 100644 pkg/golinters/gochecknoglobals/testdata/gochecknoglobals_cgo.go create mode 100644 pkg/golinters/gochecknoinits/testdata/gochecknoinits_cgo.go create mode 100644 pkg/golinters/gochecksumtype/testdata/gochecksumtype_cgo.go create mode 100644 pkg/golinters/gocognit/testdata/gocognit_cgo.go create mode 100644 pkg/golinters/goconst/testdata/goconst_cgo.go create mode 100644 pkg/golinters/gocritic/testdata/gocritic_cgo.go create mode 100644 pkg/golinters/gocyclo/testdata/gocyclo_cgo.go create mode 100644 pkg/golinters/godot/testdata/godot_cgo.go create mode 100644 pkg/golinters/godox/testdata/godox_cgo.go create mode 100644 pkg/golinters/gofmt/testdata/gofmt_cgo.go create mode 100644 pkg/golinters/gofumpt/testdata/gofumpt_cgo.go create mode 100644 pkg/golinters/goheader/testdata/goheader_cgo.go create mode 100644 pkg/golinters/goimports/testdata/goimports_cgo.go create mode 100644 pkg/golinters/gomodguard/testdata/gomodguard_cgo.go create mode 100644 pkg/golinters/goprintffuncname/testdata/goprintffuncname_cgo.go create mode 100644 pkg/golinters/gosec/testdata/gosec_cgo.go create mode 100644 pkg/golinters/gosimple/testdata/gosimple_cgo.go create mode 100644 pkg/golinters/gosmopolitan/testdata/gosmopolitan_cgo.go create mode 100644 pkg/golinters/govet/testdata/govet_cgo.go create mode 100644 pkg/golinters/grouper/testdata/grouper_cgo.go create mode 100644 pkg/golinters/iface/testdata/iface_cgo.go create mode 100644 pkg/golinters/importas/testdata/importas_cgo.go create mode 100644 pkg/golinters/inamedparam/testdata/inamedparam_cgo.go create mode 100644 pkg/golinters/ineffassign/testdata/ineffassign_cgo.go create mode 100644 pkg/golinters/interfacebloat/testdata/interfacebloat_cgo.go create mode 100644 pkg/golinters/intrange/testdata/intrange_cgo.go create mode 100644 pkg/golinters/ireturn/testdata/ireturn_cgo.go create mode 100644 pkg/golinters/lll/testdata/lll_cgo.go create mode 100644 pkg/golinters/loggercheck/testdata/loggercheck_cgo.go create mode 100644 pkg/golinters/maintidx/testdata/maintidx_cgo.go create mode 100644 pkg/golinters/makezero/testdata/makezero_cgo.go create mode 100644 pkg/golinters/mirror/testdata/mirror_cgo.go create mode 100644 pkg/golinters/misspell/testdata/misspell_cgo.go create mode 100644 pkg/golinters/mnd/testdata/mnd_cgo.go create mode 100644 pkg/golinters/musttag/testdata/musttag_cgo.go create mode 100644 pkg/golinters/nakedret/testdata/nakedret_cgo.go create mode 100644 pkg/golinters/nestif/testdata/nestif_cgo.go create mode 100644 pkg/golinters/nilerr/testdata/nilerr_cgo.go create mode 100644 pkg/golinters/nilnil/testdata/nilnil_cgo.go create mode 100644 pkg/golinters/nlreturn/testdata/nlreturn_cgo.go create mode 100644 pkg/golinters/noctx/testdata/noctx_cgo.go create mode 100644 pkg/golinters/nolintlint/testdata/nolintlint_cgo.go create mode 100644 pkg/golinters/nonamedreturns/testdata/nonamedreturns_cgo.go create mode 100644 pkg/golinters/nosprintfhostport/testdata/nosprintfhostport_cgo.go create mode 100644 pkg/golinters/paralleltest/testdata/paralleltest_cgo.go create mode 100644 pkg/golinters/perfsprint/testdata/perfsprint_cgo.go create mode 100644 pkg/golinters/prealloc/testdata/prealloc_cgo.go create mode 100644 pkg/golinters/predeclared/testdata/predeclared_cgo.go create mode 100644 pkg/golinters/promlinter/testdata/promlinter_cgo.go create mode 100644 pkg/golinters/protogetter/testdata/protogetter_cgo.go create mode 100644 pkg/golinters/reassign/testdata/reassign_cgo.go create mode 100644 pkg/golinters/recvcheck/testdata/recvcheck_cgo.go create mode 100644 pkg/golinters/revive/testdata/revive_cgo.go create mode 100644 pkg/golinters/rowserrcheck/testdata/rowserrcheck_cgo.go create mode 100644 pkg/golinters/sloglint/testdata/sloglint_cgo.go create mode 100644 pkg/golinters/spancheck/testdata/spancheck_cgo.go create mode 100644 pkg/golinters/sqlclosecheck/testdata/sqlclosecheck_cgo.go create mode 100644 pkg/golinters/staticcheck/testdata/staticcheck_cgo.go create mode 100644 pkg/golinters/stylecheck/testdata/stylecheck_cgo.go create mode 100644 pkg/golinters/tagalign/testdata/tagalign_cgo.go create mode 100644 pkg/golinters/tagliatelle/testdata/tagliatelle_cgo.go create mode 100644 pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go create mode 100644 pkg/golinters/testifylint/testdata/testifylint_cgo.go create mode 100644 pkg/golinters/thelper/testdata/thelper_cgo.go create mode 100644 pkg/golinters/tparallel/testdata/tparallel_cgo.go create mode 100644 pkg/golinters/unconvert/testdata/unconvert_cgo.go create mode 100644 pkg/golinters/unparam/testdata/unparam_cgo.go create mode 100644 pkg/golinters/unused/testdata/unused_cgo.go create mode 100644 pkg/golinters/usestdlibvars/testdata/usestdlibvars_cgo.go create mode 100644 pkg/golinters/usetesting/testdata/usetesting_cgo.go create mode 100644 pkg/golinters/varnamelen/testdata/varnamelen_cgo.go create mode 100644 pkg/golinters/wastedassign/testdata/wastedassign_cgo.go create mode 100644 pkg/golinters/whitespace/testdata/whitespace_cgo.go create mode 100644 pkg/golinters/wrapcheck/testdata/wrapcheck_cgo.go create mode 100644 pkg/golinters/wsl/testdata/wsl_cgo.go create mode 100644 pkg/golinters/zerologlint/testdata/zerologlint_cgo.go diff --git a/pkg/golinters/asasalint/testdata/asasalint_cgo.go b/pkg/golinters/asasalint/testdata/asasalint_cgo.go new file mode 100644 index 000000000000..70ef171b5227 --- /dev/null +++ b/pkg/golinters/asasalint/testdata/asasalint_cgo.go @@ -0,0 +1,39 @@ +//golangcitest:args -Easasalint +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func getArgsLength(args ...interface{}) int { + // this line will not report as error + fmt.Println(args) + return len(args) +} + +func checkArgsLength(args ...interface{}) int { + return getArgsLength(args) // want `pass \[\]any as any to func getArgsLength func\(args \.\.\.interface\{\}\)` +} + +func someCall() { + var a = []interface{}{1, 2, 3} + fmt.Println(checkArgsLength(a...) == getArgsLength(a)) // want `pass \[\]any as any to func getArgsLength func\(args \.\.\.interface\{\}\)` + fmt.Println(checkArgsLength(a...) == getArgsLength(a...)) +} diff --git a/pkg/golinters/asciicheck/testdata/asasalint_cgo.go b/pkg/golinters/asciicheck/testdata/asasalint_cgo.go new file mode 100644 index 000000000000..323990225dce --- /dev/null +++ b/pkg/golinters/asciicheck/testdata/asasalint_cgo.go @@ -0,0 +1,40 @@ +//golangcitest:args -Easciicheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type AsciicheckTะตstStruct struct { // want `identifier "AsciicheckTะตstStruct" contain non-ASCII character: U\+0435 'ะต'` + Date time.Time +} + +type AsciicheckField struct{} + +type AsciicheckJustStruct struct { + Tะตst AsciicheckField // want `identifier "Tะตst" contain non-ASCII character: U\+0435 'ะต'` +} + +func AsciicheckTะตstFunc() { // want `identifier "AsciicheckTะตstFunc" contain non-ASCII character: U\+0435 'ะต'` + var tะตstVar int // want `identifier "tะตstVar" contain non-ASCII character: U\+0435 'ะต'` + tะตstVar = 0 + fmt.Println(tะตstVar) +} diff --git a/pkg/golinters/bidichk/testdata/bidichk_cgo.go b/pkg/golinters/bidichk/testdata/bidichk_cgo.go new file mode 100644 index 000000000000..19f4ffe6bd84 --- /dev/null +++ b/pkg/golinters/bidichk/testdata/bidichk_cgo.go @@ -0,0 +1,27 @@ +//golangcitest:args -Ebidichk +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + fmt.Println("LEFT-TO-RIGHT-OVERRIDE: 'โ€ญ', it is between the single quotes, but it is not visible with a regular editor") // want "found dangerous unicode character sequence LEFT-TO-RIGHT-OVERRIDE" +} diff --git a/pkg/golinters/bodyclose/testdata/bodyclose_cgo.go b/pkg/golinters/bodyclose/testdata/bodyclose_cgo.go new file mode 100644 index 000000000000..e4b25ee29ce6 --- /dev/null +++ b/pkg/golinters/bodyclose/testdata/bodyclose_cgo.go @@ -0,0 +1,29 @@ +//golangcitest:args -Ebodyclose +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "io/ioutil" + "net/http" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func BodycloseNotClosed() { + resp, _ := http.Get("https://google.com") // want "response body must be closed" + _, _ = ioutil.ReadAll(resp.Body) +} diff --git a/pkg/golinters/canonicalheader/testdata/canonicalheader_cgo.go b/pkg/golinters/canonicalheader/testdata/canonicalheader_cgo.go new file mode 100644 index 000000000000..c2d1fc3d229d --- /dev/null +++ b/pkg/golinters/canonicalheader/testdata/canonicalheader_cgo.go @@ -0,0 +1,40 @@ +//golangcitest:args -Ecanonicalheader +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "net/http" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func canonicalheader() { + v := http.Header{} + + v.Get("Test-HEader") // want `non-canonical header "Test-HEader", instead use: "Test-Header"` + v.Set("Test-HEader", "value") // want `non-canonical header "Test-HEader", instead use: "Test-Header"` + v.Add("Test-HEader", "value") // want `non-canonical header "Test-HEader", instead use: "Test-Header"` + v.Del("Test-HEader") // want `non-canonical header "Test-HEader", instead use: "Test-Header"` + v.Values("Test-HEader") // want `non-canonical header "Test-HEader", instead use: "Test-Header"` + + v.Values("Sec-WebSocket-Accept") + + v.Set("Test-Header", "value") + v.Add("Test-Header", "value") + v.Del("Test-Header") + v.Values("Test-Header") +} diff --git a/pkg/golinters/containedctx/testdata/containedctx_cgo.go b/pkg/golinters/containedctx/testdata/containedctx_cgo.go new file mode 100644 index 000000000000..67190f5cd811 --- /dev/null +++ b/pkg/golinters/containedctx/testdata/containedctx_cgo.go @@ -0,0 +1,34 @@ +//golangcitest:args -Econtainedctx +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type ok struct { + i int + s string +} + +type ng struct { + ctx context.Context // want "found a struct that contains a context.Context field" +} + +type empty struct{} diff --git a/pkg/golinters/contextcheck/testdata/contextcheck_cgo.go b/pkg/golinters/contextcheck/testdata/contextcheck_cgo.go new file mode 100644 index 000000000000..dfec882bae18 --- /dev/null +++ b/pkg/golinters/contextcheck/testdata/contextcheck_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Econtextcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func contextcheckCase1(ctx context.Context) { + funcWithoutCtx() // want "Function `funcWithoutCtx` should pass the context parameter" +} + +func funcWithCtx(ctx context.Context) {} + +func funcWithoutCtx() { + funcWithCtx(context.TODO()) +} diff --git a/pkg/golinters/copyloopvar/testdata/copyloopvar_cgo.go b/pkg/golinters/copyloopvar/testdata/copyloopvar_cgo.go new file mode 100644 index 000000000000..cf36a94b5db8 --- /dev/null +++ b/pkg/golinters/copyloopvar/testdata/copyloopvar_cgo.go @@ -0,0 +1,39 @@ +//go:build go1.22 + +//golangcitest:args -Ecopyloopvar +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func copyloopvarCase2() { + loopCount := 3 + fns := make([]func(), 0, loopCount) + for i := 1; i <= loopCount; i++ { + i := i // want `The copy of the 'for' variable "i" can be deleted \(Go 1\.22\+\)` + fns = append(fns, func() { + fmt.Println(i) + }) + } + for _, fn := range fns { + fn() + } +} diff --git a/pkg/golinters/cyclop/testdata/cyclop_cgo.go b/pkg/golinters/cyclop/testdata/cyclop_cgo.go new file mode 100644 index 000000000000..118a4d49a2cd --- /dev/null +++ b/pkg/golinters/cyclop/testdata/cyclop_cgo.go @@ -0,0 +1,35 @@ +//golangcitest:args -Ecyclop +//golangcitest:config_path testdata/cyclop.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func cyclopComplexFunc(s string) { // want "calculated cyclomatic complexity for function cyclopComplexFunc is 22, max is 15" + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } +} diff --git a/pkg/golinters/decorder/testdata/decorder_cgo.go b/pkg/golinters/decorder/testdata/decorder_cgo.go new file mode 100644 index 000000000000..db0915c834ce --- /dev/null +++ b/pkg/golinters/decorder/testdata/decorder_cgo.go @@ -0,0 +1,40 @@ +//golangcitest:args -Edecorder +//golangcitest:config_path testdata/decorder_custom.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +const ( + decoc = math.MaxInt64 + decod = 1 +) + +var decoa = 1 +var decob = 1 // want "multiple \"var\" declarations are not allowed; use parentheses instead" + +type decoe int // want "type must not be placed after const" + +func decof() { + const decog = 1 +} + +func init() {} // want "init func must be the first function in file" + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} diff --git a/pkg/golinters/depguard/testdata/depguard_cgo.go b/pkg/golinters/depguard/testdata/depguard_cgo.go new file mode 100644 index 000000000000..6d9efc7fe00f --- /dev/null +++ b/pkg/golinters/depguard/testdata/depguard_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Edepguard +//golangcitest:config_path testdata/depguard.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "compress/gzip" // want "import 'compress/gzip' is not allowed from list 'main': nope" + "log" // want "import 'log' is not allowed from list 'main': don't use log" + "unsafe" + + "golang.org/x/tools/go/analysis" // want "import 'golang.org/x/tools/go/analysis' is not allowed from list 'main': example import with dot" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func SpewDebugInfo() { + log.Println(gzip.BestCompression) + _ = analysis.Analyzer{} +} diff --git a/pkg/golinters/dogsled/testdata/dogsled_cgo.go b/pkg/golinters/dogsled/testdata/dogsled_cgo.go new file mode 100644 index 000000000000..ae1995c91cf5 --- /dev/null +++ b/pkg/golinters/dogsled/testdata/dogsled_cgo.go @@ -0,0 +1,45 @@ +//golangcitest:args -Edogsled +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + _ = ret1() + _, _ = ret2() + _, _, _ = ret3() // want "declaration has 3 blank identifiers" + _, _, _, _ = ret4() // want "declaration has 4 blank identifiers" +} + +func ret1() (a int) { + return 1 +} + +func ret2() (a, b int) { + return 1, 2 +} + +func ret3() (a, b, c int) { + return 1, 2, 3 +} + +func ret4() (a, b, c, d int) { + return 1, 2, 3, 4 +} diff --git a/pkg/golinters/dupl/testdata/dupl_cgo.go b/pkg/golinters/dupl/testdata/dupl_cgo.go new file mode 100644 index 000000000000..26168133baa5 --- /dev/null +++ b/pkg/golinters/dupl/testdata/dupl_cgo.go @@ -0,0 +1,54 @@ +//golangcitest:args -Edupl +//golangcitest:config_path testdata/dupl.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type DuplLogger struct{} + +func (DuplLogger) level() int { + return 1 +} + +func (DuplLogger) Debug(args ...interface{}) {} +func (DuplLogger) Info(args ...interface{}) {} + +func (logger *DuplLogger) First(args ...interface{}) { // want "34-43 lines are duplicate of `.*dupl_cgo.go:45-54`" + if logger.level() >= 0 { + logger.Debug(args...) + logger.Debug(args...) + logger.Debug(args...) + logger.Debug(args...) + logger.Debug(args...) + logger.Debug(args...) + } +} + +func (logger *DuplLogger) Second(args ...interface{}) { // want "45-54 lines are duplicate of `.*dupl_cgo.go:34-43`" + if logger.level() >= 1 { + logger.Info(args...) + logger.Info(args...) + logger.Info(args...) + logger.Info(args...) + logger.Info(args...) + logger.Info(args...) + } +} diff --git a/pkg/golinters/dupword/testdata/dupword_cgo.go b/pkg/golinters/dupword/testdata/dupword_cgo.go new file mode 100644 index 000000000000..4e02ec22d124 --- /dev/null +++ b/pkg/golinters/dupword/testdata/dupword_cgo.go @@ -0,0 +1,28 @@ +//golangcitest:args -Edupword +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func duplicateWordInComments() { + // this line include duplicated word the the // want `Duplicate words \(the\) found` + fmt.Println("hello") +} diff --git a/pkg/golinters/durationcheck/testdata/durationcheck_cgo.go b/pkg/golinters/durationcheck/testdata/durationcheck_cgo.go new file mode 100644 index 000000000000..97cbfadd8760 --- /dev/null +++ b/pkg/golinters/durationcheck/testdata/durationcheck_cgo.go @@ -0,0 +1,38 @@ +//golangcitest:args -Edurationcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type durationCheckData struct { + i int + d time.Duration +} + +func durationcheckCase01() { + dcd := durationCheckData{i: 10} + _ = time.Duration(dcd.i) * time.Second +} + +func durationcheckCase02() { + dcd := durationCheckData{d: 10 * time.Second} + _ = dcd.d * time.Second // want "Multiplication of durations: `dcd.d \\* time.Second`" +} diff --git a/pkg/golinters/err113/testdata/err113_cgo.go b/pkg/golinters/err113/testdata/err113_cgo.go new file mode 100644 index 000000000000..c1356502cffc --- /dev/null +++ b/pkg/golinters/err113/testdata/err113_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Eerr113 +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "os" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func CheckGoerr13Import(e error) bool { + f, err := os.Create("f.txt") + if err != nil { + return err == e // want `do not compare errors directly "err == e", use "errors.Is\(err, e\)" instead` + } + f.Close() + return false +} diff --git a/pkg/golinters/errcheck/testdata/errcheck_cgo.go b/pkg/golinters/errcheck/testdata/errcheck_cgo.go new file mode 100644 index 000000000000..e2f7f0100d76 --- /dev/null +++ b/pkg/golinters/errcheck/testdata/errcheck_cgo.go @@ -0,0 +1,30 @@ +//golangcitest:args -Eerrcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func RetErr() error { + return nil +} + +func MissedErrorCheck() { + RetErr() // want "Error return value is not checked" +} diff --git a/pkg/golinters/errchkjson/testdata/errchkjson_cgo.go b/pkg/golinters/errchkjson/testdata/errchkjson_cgo.go new file mode 100644 index 000000000000..ba14c127344d --- /dev/null +++ b/pkg/golinters/errchkjson/testdata/errchkjson_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Eerrchkjson +//golangcitest:config_path testdata/errchkjson.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "encoding/json" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + var err error + + _, _ = json.Marshal(nil) // want "Error return value of `encoding/json.Marshal` is not checked" + json.Marshal(nil) // want "Error return value of `encoding/json.Marshal` is not checked" + _, err = json.Marshal(nil) // nil is safe and check-error-free-encoding is false + _ = err +} diff --git a/pkg/golinters/errname/testdata/errname_cgo.go b/pkg/golinters/errname/testdata/errname_cgo.go new file mode 100644 index 000000000000..a1cb22df7b03 --- /dev/null +++ b/pkg/golinters/errname/testdata/errname_cgo.go @@ -0,0 +1,35 @@ +//golangcitest:args -Eerrname +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "errors" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +var ( + EOF = errors.New("end of file") + ErrEndOfFile = errors.New("end of file") + errEndOfFile = errors.New("end of file") + + EndOfFileError = errors.New("end of file") // want "the sentinel error name `EndOfFileError` should conform to the `ErrXxx` format" + ErrorEndOfFile = errors.New("end of file") // want "the sentinel error name `ErrorEndOfFile` should conform to the `ErrXxx` format" + EndOfFileErr = errors.New("end of file") // want "the sentinel error name `EndOfFileErr` should conform to the `ErrXxx` format" + endOfFileError = errors.New("end of file") // want "the sentinel error name `endOfFileError` should conform to the `errXxx` format" + errorEndOfFile = errors.New("end of file") // want "the sentinel error name `errorEndOfFile` should conform to the `errXxx` format" +) diff --git a/pkg/golinters/errorlint/testdata/errorlint_cgo.go b/pkg/golinters/errorlint/testdata/errorlint_cgo.go new file mode 100644 index 000000000000..85d35df37935 --- /dev/null +++ b/pkg/golinters/errorlint/testdata/errorlint_cgo.go @@ -0,0 +1,48 @@ +//golangcitest:args -Eerrorlint +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "errors" + "fmt" + "log" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +var errLintFoo = errors.New("foo") + +type errLintBar struct{} + +func (*errLintBar) Error() string { + return "bar" +} + +func errorLintAll() { + err := func() error { return nil }() + if err == errLintFoo { // want "comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error" + log.Println("errCompare") + } + + err = errors.New("oops") + fmt.Errorf("error: %v", err) // want "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors" + + switch err.(type) { // want "type switch on error will fail on wrapped errors. Use errors.As to check for specific errors" + case *errLintBar: + log.Println("errLintBar") + } +} diff --git a/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go b/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go new file mode 100644 index 000000000000..4cac543fba4c --- /dev/null +++ b/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go @@ -0,0 +1,37 @@ +//golangcitest:args -Eexhaustive +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type Direction int + +const ( + North Direction = iota + East + South + West +) + +func processDirection(d Direction) { + switch d { // want "missing cases in switch of type testdata.Direction: testdata.East, testdata.West" + case North, South: + } +} diff --git a/pkg/golinters/exhaustruct/testdata/exhaustruct_cgo.go b/pkg/golinters/exhaustruct/testdata/exhaustruct_cgo.go new file mode 100644 index 000000000000..0984e5def8d3 --- /dev/null +++ b/pkg/golinters/exhaustruct/testdata/exhaustruct_cgo.go @@ -0,0 +1,66 @@ +//golangcitest:args -Eexhaustruct +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type Exhaustruct struct { + A string + B int + c bool // private field inside the same package are not ignored + D float64 + E time.Time +} + +func exhaustruct() { + // pass + _ = Exhaustruct{ + A: "a", + B: 0, + c: false, + D: 1.0, + E: time.Now(), + } + + // failPrivate + _ = Exhaustruct{ // want "testdata.Exhaustruct is missing field c" + A: "a", + B: 0, + D: 1.0, + E: time.Now(), + } + + // fail + _ = Exhaustruct{ // want "testdata.Exhaustruct is missing field B" + A: "a", + c: false, + D: 1.0, + E: time.Now(), + } + + // failMultiple + _ = Exhaustruct{ // want "testdata.Exhaustruct is missing fields B, D" + A: "a", + c: false, + E: time.Now(), + } + +} diff --git a/pkg/golinters/exportloopref/testdata/exportloopref_cgo.go b/pkg/golinters/exportloopref/testdata/exportloopref_cgo.go new file mode 100644 index 000000000000..22d0a2bdcc98 --- /dev/null +++ b/pkg/golinters/exportloopref/testdata/exportloopref_cgo.go @@ -0,0 +1,65 @@ +//golangcitest:args -Eexportloopref +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func dummyFunction() { + var array [4]*int + var slice []*int + var ref *int + var str struct{ x *int } + + fmt.Println("loop expecting 10, 11, 12, 13") + for i, p := range []int{10, 11, 12, 13} { + printp(&p) + slice = append(slice, &p) // want "exporting a pointer for the loop variable p" + array[i] = &p // want "exporting a pointer for the loop variable p" + if i%2 == 0 { + ref = &p // want "exporting a pointer for the loop variable p" + str.x = &p // want "exporting a pointer for the loop variable p" + } + var vStr struct{ x *int } + var vArray [4]*int + var v *int + if i%2 == 0 { + v = &p + vArray[1] = &p + vStr.x = &p + } + _ = v + } + + fmt.Println(`slice expecting "10, 11, 12, 13" but "13, 13, 13, 13"`) + for _, p := range slice { + printp(p) + } + fmt.Println(`array expecting "10, 11, 12, 13" but "13, 13, 13, 13"`) + for _, p := range array { + printp(p) + } + fmt.Println(`captured value expecting "12" but "13"`) + printp(ref) +} + +func printp(p *int) { + fmt.Println(*p) +} diff --git a/pkg/golinters/fatcontext/testdata/fatcontext_cgo.go b/pkg/golinters/fatcontext/testdata/fatcontext_cgo.go new file mode 100644 index 000000000000..2be3b5d76935 --- /dev/null +++ b/pkg/golinters/fatcontext/testdata/fatcontext_cgo.go @@ -0,0 +1,52 @@ +//golangcitest:args -Efatcontext +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + ctx := context.Background() + + for i := 0; i < 10; i++ { + ctx := context.WithValue(ctx, "key", i) + ctx = context.WithValue(ctx, "other", "val") + } + + for i := 0; i < 10; i++ { + ctx = context.WithValue(ctx, "key", i) // want "nested context in loop" + ctx = context.WithValue(ctx, "other", "val") + } + + for item := range []string{"one", "two", "three"} { + ctx = wrapContext(ctx) // want "nested context in loop" + ctx := context.WithValue(ctx, "key", item) + ctx = wrapContext(ctx) + } + + for { + ctx = wrapContext(ctx) // want "nested context in loop" + break + } +} + +func wrapContext(ctx context.Context) context.Context { + return context.WithoutCancel(ctx) +} diff --git a/pkg/golinters/forbidigo/testdata/forbidigo_cgo.go b/pkg/golinters/forbidigo/testdata/forbidigo_cgo.go new file mode 100644 index 000000000000..f9ecbfa91cf9 --- /dev/null +++ b/pkg/golinters/forbidigo/testdata/forbidigo_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Eforbidigo +//golangcitest:config_path testdata/forbidigo.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + fmt2 "fmt" + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Forbidigo() { + fmt.Printf("too noisy!!!") // want "use of `fmt\\.Printf` forbidden by pattern `fmt\\\\.Print\\.\\*`" + fmt2.Printf("too noisy!!!") // Not detected because analyze-types is false by default for backward compatibility. + time.Sleep(time.Nanosecond) // want "no sleeping!" +} diff --git a/pkg/golinters/forcetypeassert/testdata/forcetypeassert_cgo.go b/pkg/golinters/forcetypeassert/testdata/forcetypeassert_cgo.go new file mode 100644 index 000000000000..6615383d77ce --- /dev/null +++ b/pkg/golinters/forcetypeassert/testdata/forcetypeassert_cgo.go @@ -0,0 +1,39 @@ +//golangcitest:args -Eforcetypeassert +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + var a interface{} + _ = a.(int) // want "type assertion must be checked" + + var b interface{} + bi := b.(int) // want "type assertion must be checked" + fmt.Println(bi) +} + +func _() { + var a interface{} + if ai, ok := a.(int); ok { + fmt.Println(ai) + } +} diff --git a/pkg/golinters/funlen/testdata/funlen_cgo.go b/pkg/golinters/funlen/testdata/funlen_cgo.go new file mode 100644 index 000000000000..9bd4247ee057 --- /dev/null +++ b/pkg/golinters/funlen/testdata/funlen_cgo.go @@ -0,0 +1,48 @@ +//golangcitest:args -Efunlen +//golangcitest:config_path testdata/funlen.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { // want `Function '_' is too long \(22 > 20\)` + t := struct { + A string + B string + C string + D string + E string + F string + G string + H string + I string + }{ + `a`, + `b`, + `c`, + `d`, + `e`, + `f`, + `g`, + `h`, + `i`, + } + _ = t +} diff --git a/pkg/golinters/gci/testdata/gci_cgo.go b/pkg/golinters/gci/testdata/gci_cgo.go new file mode 100644 index 000000000000..8fe72c42e0e5 --- /dev/null +++ b/pkg/golinters/gci/testdata/gci_cgo.go @@ -0,0 +1,36 @@ +//golangcitest:args -Egci +//golangcitest:config_path testdata/gci.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +// want +1 "Invalid import order" +import ( + "golang.org/x/tools/go/analysis" + "github.com/golangci/golangci-lint/pkg/config" + "unsafe" + "fmt" + "errors" + gcicfg "github.com/daixiang0/gci/pkg/config" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func GoimportsLocalTest() { + fmt.Print(errors.New("x")) + _ = config.Config{} + _ = analysis.Analyzer{} + _ = gcicfg.BoolConfig{} +} diff --git a/pkg/golinters/ginkgolinter/testdata/ginkgolinter_cgo.go b/pkg/golinters/ginkgolinter/testdata/ginkgolinter_cgo.go new file mode 100644 index 000000000000..5baff8729195 --- /dev/null +++ b/pkg/golinters/ginkgolinter/testdata/ginkgolinter_cgo.go @@ -0,0 +1,41 @@ +//golangcitest:args --disable-all -Eginkgolinter +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" + + . "github.com/onsi/gomega" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func LenUsecase() { + var fakeVarUnderTest []int + Expect(fakeVarUnderTest).Should(BeEmpty()) // valid + Expect(fakeVarUnderTest).ShouldNot(HaveLen(5)) // valid + + Expect(len(fakeVarUnderTest)).Should(Equal(0)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.Should\\(BeEmpty\\(\\)\\)" + Expect(len(fakeVarUnderTest)).ShouldNot(Equal(2)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.ShouldNot\\(HaveLen\\(2\\)\\)" + Expect(len(fakeVarUnderTest)).To(BeNumerically("==", 0)) // // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.To\\(BeEmpty\\(\\)\\)" + + fakeVarUnderTest = append(fakeVarUnderTest, 3) + Expect(len(fakeVarUnderTest)).ShouldNot(Equal(0)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.ShouldNot\\(BeEmpty\\(\\)\\)" + Expect(len(fakeVarUnderTest)).Should(Equal(1)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.Should\\(HaveLen\\(1\\)\\)" + Expect(len(fakeVarUnderTest)).To(BeNumerically(">", 0)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.ToNot\\(BeEmpty\\(\\)\\)" + Expect(len(fakeVarUnderTest)).To(BeNumerically(">=", 1)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.ToNot\\(BeEmpty\\(\\)\\)" + Expect(len(fakeVarUnderTest)).To(BeNumerically("!=", 0)) // want "ginkgo-linter: wrong length assertion. Consider using .Expect\\(fakeVarUnderTest\\)\\.ToNot\\(BeEmpty\\(\\)\\)" +} diff --git a/pkg/golinters/gocheckcompilerdirectives/testdata/gocheckcompilerdirectives_cgo.go b/pkg/golinters/gocheckcompilerdirectives/testdata/gocheckcompilerdirectives_cgo.go new file mode 100644 index 000000000000..3f07288d5ba0 --- /dev/null +++ b/pkg/golinters/gocheckcompilerdirectives/testdata/gocheckcompilerdirectives_cgo.go @@ -0,0 +1,40 @@ +//golangcitest:args -Egocheckcompilerdirectives +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" + _ "embed" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// Okay cases: + +//go:generate echo hello world + +//go:embed +var Value string + +//go: + +// Problematic cases: + +// go:embed // want "compiler directive contains space: // go:embed" + +// go:embed // want "compiler directive contains space: // go:embed" + +//go:genrate // want "compiler directive unrecognized: //go:genrate" diff --git a/pkg/golinters/gochecknoglobals/testdata/gochecknoglobals_cgo.go b/pkg/golinters/gochecknoglobals/testdata/gochecknoglobals_cgo.go new file mode 100644 index 000000000000..a94c589134f9 --- /dev/null +++ b/pkg/golinters/gochecknoglobals/testdata/gochecknoglobals_cgo.go @@ -0,0 +1,37 @@ +//golangcitest:args -Egochecknoglobals +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "errors" + "fmt" + "regexp" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +var noGlobalsVar int // want "noGlobalsVar is a global variable" +var ErrSomeType = errors.New("test that global errors aren't warned") + +var ( + OnlyDigits = regexp.MustCompile(`^\d+$`) + BadNamedErr = errors.New("this is bad") // want "BadNamedErr is a global variable" +) + +func NoGlobals() { + fmt.Print(noGlobalsVar) +} diff --git a/pkg/golinters/gochecknoinits/testdata/gochecknoinits_cgo.go b/pkg/golinters/gochecknoinits/testdata/gochecknoinits_cgo.go new file mode 100644 index 000000000000..d003c59f30a7 --- /dev/null +++ b/pkg/golinters/gochecknoinits/testdata/gochecknoinits_cgo.go @@ -0,0 +1,29 @@ +//golangcitest:args -Egochecknoinits +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func init() { // want "don't use `init` function" + fmt.Println() +} + +func Init() {} diff --git a/pkg/golinters/gochecksumtype/testdata/gochecksumtype_cgo.go b/pkg/golinters/gochecksumtype/testdata/gochecksumtype_cgo.go new file mode 100644 index 000000000000..b4f835fc291d --- /dev/null +++ b/pkg/golinters/gochecksumtype/testdata/gochecksumtype_cgo.go @@ -0,0 +1,61 @@ +//golangcitest:args -Egochecksumtype +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "log" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +//sumtype:decl +type SumType interface{ isSumType() } + +//sumtype:decl +type One struct{} // want "type 'One' is not an interface" + +func (One) isSumType() {} + +type Two struct{} + +func (Two) isSumType() {} + +func sumTypeTest() { + var sum SumType = One{} + switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two" + case One: + } + + switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two" + case One: + default: + panic("??") + } + + switch sum.(type) { + case *One: + default: + log.Println("legit catch all goes here") + } + + log.Println("??") + + switch sum.(type) { + case One: + case Two: + } +} diff --git a/pkg/golinters/gocognit/testdata/gocognit_cgo.go b/pkg/golinters/gocognit/testdata/gocognit_cgo.go new file mode 100644 index 000000000000..1bbbc7fb9956 --- /dev/null +++ b/pkg/golinters/gocognit/testdata/gocognit_cgo.go @@ -0,0 +1,35 @@ +//golangcitest:args -Egocognit +//golangcitest:config_path testdata/gocognit.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(number int) string { // want "cognitive complexity 4 of func .* is high .*" + if number == 1 { // +1 + return "one" + } else if number == 2 { // +1 + return "a couple" + } else if number == 3 { // +1 + return "a few" + } else { // +1 + return "lots" + } +} // total complexity = 4 diff --git a/pkg/golinters/goconst/testdata/goconst_cgo.go b/pkg/golinters/goconst/testdata/goconst_cgo.go new file mode 100644 index 000000000000..632dd2d5a2ce --- /dev/null +++ b/pkg/golinters/goconst/testdata/goconst_cgo.go @@ -0,0 +1,39 @@ +//golangcitest:args -Egoconst +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + a := "needconst" // want "string `needconst` has 5 occurrences, make it a constant" + fmt.Print(a) + b := "needconst" + fmt.Print(b) + c := "needconst" + fmt.Print(c) +} + +func _() { + a := "needconst" + fmt.Print(a) + b := "needconst" + fmt.Print(b) +} diff --git a/pkg/golinters/gocritic/testdata/gocritic_cgo.go b/pkg/golinters/gocritic/testdata/gocritic_cgo.go new file mode 100644 index 000000000000..a1bba77b59c2 --- /dev/null +++ b/pkg/golinters/gocritic/testdata/gocritic_cgo.go @@ -0,0 +1,26 @@ +//golangcitest:args -Egocritic +//golangcitest:config_path testdata/gocritic.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "flag" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +var _ = *flag.Bool("global1", false, "") // want `flagDeref: immediate deref in \*flag.Bool\(.global1., false, ..\) is most likely an error; consider using flag\.BoolVar` diff --git a/pkg/golinters/gocyclo/testdata/gocyclo_cgo.go b/pkg/golinters/gocyclo/testdata/gocyclo_cgo.go new file mode 100644 index 000000000000..09b0e580e2ae --- /dev/null +++ b/pkg/golinters/gocyclo/testdata/gocyclo_cgo.go @@ -0,0 +1,38 @@ +//golangcitest:args -Egocyclo +//golangcitest:config_path testdata/gocyclo.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "net/http" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(s string) { // want "cyclomatic complexity .* of func .* is high .*" + if s == http.MethodGet || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } + + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } + + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } +} diff --git a/pkg/golinters/godot/testdata/godot_cgo.go b/pkg/golinters/godot/testdata/godot_cgo.go new file mode 100644 index 000000000000..89f674fc18e3 --- /dev/null +++ b/pkg/golinters/godot/testdata/godot_cgo.go @@ -0,0 +1,29 @@ +//golangcitest:args -Egodot +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// want +2 "Comment should end in a period" + +// Godot checks top-level comments +func Godot() { + // nothing to do here +} diff --git a/pkg/golinters/godox/testdata/godox_cgo.go b/pkg/golinters/godox/testdata/godox_cgo.go new file mode 100644 index 000000000000..05ce270356c2 --- /dev/null +++ b/pkg/golinters/godox/testdata/godox_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Egodox +//golangcitest:config_path testdata/godox.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func todoLeftInCode() { + // TODO implement me // want `Line contains FIXME/TODO: "TODO implement me` + //TODO no space // want `Line contains FIXME/TODO: "TODO no space` + // TODO(author): 123 // want `Line contains FIXME/TODO: "TODO\(author\): 123` + //TODO(author): 123 // want `Line contains FIXME/TODO: "TODO\(author\): 123` + //TODO(author) 456 // want `Line contains FIXME/TODO: "TODO\(author\) 456` + // TODO: qwerty // want `Line contains FIXME/TODO: "TODO: qwerty` + // todo 789 // want `Line contains FIXME/TODO: "todo 789` +} diff --git a/pkg/golinters/gofmt/testdata/gofmt_cgo.go b/pkg/golinters/gofmt/testdata/gofmt_cgo.go new file mode 100644 index 000000000000..9effc9f5a9d5 --- /dev/null +++ b/pkg/golinters/gofmt/testdata/gofmt_cgo.go @@ -0,0 +1,28 @@ +//golangcitest:args -Egofmt +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func GofmtNotSimplified() { + var x []string + fmt.Print(x[1:len(x)]) // want "File is not `gofmt`-ed with `-s`" +} diff --git a/pkg/golinters/gofumpt/testdata/gofumpt_cgo.go b/pkg/golinters/gofumpt/testdata/gofumpt_cgo.go new file mode 100644 index 000000000000..22baf8fb45ca --- /dev/null +++ b/pkg/golinters/gofumpt/testdata/gofumpt_cgo.go @@ -0,0 +1,27 @@ +//golangcitest:args -Egofumpt +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func GofumptNewLine() { + fmt.Println( "foo" ) // want "File is not `gofumpt`-ed" +} diff --git a/pkg/golinters/goheader/testdata/goheader_cgo.go b/pkg/golinters/goheader/testdata/goheader_cgo.go new file mode 100644 index 000000000000..0727260f0092 --- /dev/null +++ b/pkg/golinters/goheader/testdata/goheader_cgo.go @@ -0,0 +1,25 @@ +/*MY TITLE!*/ // want `Expected:TITLE\., Actual: TITLE!` + +//golangcitest:args -Egoheader +//golangcitest:config_path testdata/goheader.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} diff --git a/pkg/golinters/goimports/testdata/goimports_cgo.go b/pkg/golinters/goimports/testdata/goimports_cgo.go new file mode 100644 index 000000000000..380ae1061e76 --- /dev/null +++ b/pkg/golinters/goimports/testdata/goimports_cgo.go @@ -0,0 +1,29 @@ +//golangcitest:args -Egoimports +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" // want "File is not `goimports`-ed" + "github.com/golangci/golangci-lint/pkg/config" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Bar() { + fmt.Print("x") + _ = config.Config{} +} diff --git a/pkg/golinters/gomodguard/testdata/gomodguard_cgo.go b/pkg/golinters/gomodguard/testdata/gomodguard_cgo.go new file mode 100644 index 000000000000..4051c4638514 --- /dev/null +++ b/pkg/golinters/gomodguard/testdata/gomodguard_cgo.go @@ -0,0 +1,44 @@ +//golangcitest:args -Egomodguard +//golangcitest:config_path testdata/gomodguard.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "log" + "unsafe" + + "golang.org/x/mod/modfile" + "gopkg.in/yaml.v3" // want "import of package `gopkg.in/yaml.v3` is blocked because the module is in the blocked modules list. `github.com/kylelemons/go-gypsy` is a recommended module. This is an example of recommendations." +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// Something just some struct +type Something struct{} + +func aAllowedImport() { //nolint:unused + mfile, _ := modfile.Parse("go.mod", []byte{}, nil) + + log.Println(mfile) +} + +func aBlockedImport() { //nolint:unused + data := []byte{} + something := Something{} + _ = yaml.Unmarshal(data, &something) + + log.Println(data) +} diff --git a/pkg/golinters/goprintffuncname/testdata/goprintffuncname_cgo.go b/pkg/golinters/goprintffuncname/testdata/goprintffuncname_cgo.go new file mode 100644 index 000000000000..271d962d017f --- /dev/null +++ b/pkg/golinters/goprintffuncname/testdata/goprintffuncname_cgo.go @@ -0,0 +1,25 @@ +//golangcitest:args -Egoprintffuncname +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func PrintfLikeFuncWithBadName(format string, args ...interface{}) { // want "printf-like formatting function 'PrintfLikeFuncWithBadName' should be named 'PrintfLikeFuncWithBadNamef'" +} diff --git a/pkg/golinters/gosec/testdata/gosec_cgo.go b/pkg/golinters/gosec/testdata/gosec_cgo.go new file mode 100644 index 000000000000..7b049892a7b8 --- /dev/null +++ b/pkg/golinters/gosec/testdata/gosec_cgo.go @@ -0,0 +1,29 @@ +//golangcitest:args -Egosec +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "crypto/md5" + "log" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Gosec() { + h := md5.New() // want "G401: Use of weak cryptographic primitive" + log.Print(h) +} diff --git a/pkg/golinters/gosimple/testdata/gosimple_cgo.go b/pkg/golinters/gosimple/testdata/gosimple_cgo.go new file mode 100644 index 000000000000..99f1cebf30b1 --- /dev/null +++ b/pkg/golinters/gosimple/testdata/gosimple_cgo.go @@ -0,0 +1,31 @@ +//golangcitest:args -Egosimple +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "log" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(ss []string) { + if ss != nil { // want "S1031: unnecessary nil check around range" + for _, s := range ss { + log.Printf(s) + } + } +} diff --git a/pkg/golinters/gosmopolitan/testdata/gosmopolitan_cgo.go b/pkg/golinters/gosmopolitan/testdata/gosmopolitan_cgo.go new file mode 100644 index 000000000000..8a8323179ae9 --- /dev/null +++ b/pkg/golinters/gosmopolitan/testdata/gosmopolitan_cgo.go @@ -0,0 +1,42 @@ +//golangcitest:args -Egosmopolitan +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type col struct { + // struct tag should not get reported + Foo string `gorm:"column:bar;not null;comment:'ไธๅบ”่ฏฅๆŠฅๅ‘Š่ฟ™ไธ€่กŒ'"` +} + +func _() { + fmt.Println("hello world") + fmt.Println("ไฝ ๅฅฝ๏ผŒไธ–็•Œ") // want `string literal contains rune in Han script` + fmt.Println("ใ“ใ‚“ใซใกใฏใ€ใ‚ปใ‚ซใ‚ค") + + _ = col{Foo: "hello"} + _ = col{Foo: "ไฝ ๅฅฝ"} // want `string literal contains rune in Han script` + + x := time.Local // want `usage of time.Local` + _ = time.Now().In(x) + _ = time.Date(2023, 1, 2, 3, 4, 5, 678901234, time.Local) // want `usage of time.Local` +} diff --git a/pkg/golinters/govet/testdata/govet_cgo.go b/pkg/golinters/govet/testdata/govet_cgo.go new file mode 100644 index 000000000000..15f80ff76189 --- /dev/null +++ b/pkg/golinters/govet/testdata/govet_cgo.go @@ -0,0 +1,36 @@ +//golangcitest:args -Egovet +//golangcitest:config_path testdata/govet.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "io" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(f io.Reader, buf []byte) (err error) { + if f != nil { + _, err := f.Read(buf) // want `shadow: declaration of .err. shadows declaration at line \d+` + if err != nil { + return err + } + } + // Use variable to trigger shadowing error + _ = err + return +} diff --git a/pkg/golinters/grouper/testdata/grouper_cgo.go b/pkg/golinters/grouper/testdata/grouper_cgo.go new file mode 100644 index 000000000000..cdf4d0eb52a8 --- /dev/null +++ b/pkg/golinters/grouper/testdata/grouper_cgo.go @@ -0,0 +1,21 @@ +//golangcitest:args -Egrouper +//golangcitest:config_path testdata/grouper.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" // want "should only use grouped 'import' declarations" + +import "unsafe" // want "grouper\\(related information\\): found here" + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} diff --git a/pkg/golinters/iface/testdata/iface_cgo.go b/pkg/golinters/iface/testdata/iface_cgo.go new file mode 100644 index 000000000000..43ef0216a036 --- /dev/null +++ b/pkg/golinters/iface/testdata/iface_cgo.go @@ -0,0 +1,86 @@ +//golangcitest:args -Eiface +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// identical + +type Pinger interface { // want "identical: interface Pinger contains identical methods or type constraints from another interface, causing redundancy" + Ping() error +} + +type Healthcheck interface { // want "identical: interface Healthcheck contains identical methods or type constraints from another interface, causing redundancy" + Ping() error +} + +// opaque + +type Server interface { + Serve() error +} + +type server struct { + addr string +} + +func (s server) Serve() error { + return nil +} + +func NewServer(addr string) Server { + return &server{addr: addr} +} + +// unused + +type User struct { + ID string + Name string +} + +type UserRepository interface { + UserOf(id string) (*User, error) +} + +type UserRepositorySQL struct { +} + +func (r *UserRepositorySQL) UserOf(id string) (*User, error) { + return nil, nil +} + +type Granter interface { + Grant(permission string) error +} + +func AllowAll(g Granter) error { + return g.Grant("all") +} + +type Allower interface { + Allow(permission string) error +} + +func Allow(x any) { + _ = x.(Allower) + fmt.Println("allow") +} diff --git a/pkg/golinters/importas/testdata/importas_cgo.go b/pkg/golinters/importas/testdata/importas_cgo.go new file mode 100644 index 000000000000..930c1767e534 --- /dev/null +++ b/pkg/golinters/importas/testdata/importas_cgo.go @@ -0,0 +1,35 @@ +//golangcitest:args -Eimportas +//golangcitest:config_path testdata/importas.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + wrong_alias "fmt" // want `import "fmt" imported as "wrong_alias" but must be "fff" according to config` + "os" + wrong_alias_again "os" // want `import "os" imported as "wrong_alias_again" but must be "std_os" according to config` + "unsafe" + + wrong "golang.org/x/tools/go/analysis" // want `import "golang.org/x/tools/go/analysis" imported as "wrong" but must be "ananas" according to config` +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + wrong_alias.Println("foo") + wrong_alias_again.Stdout.WriteString("bar") + os.Stdout.WriteString("test") + _ = wrong.Analyzer{} +} diff --git a/pkg/golinters/inamedparam/testdata/inamedparam_cgo.go b/pkg/golinters/inamedparam/testdata/inamedparam_cgo.go new file mode 100644 index 000000000000..204921ad14f4 --- /dev/null +++ b/pkg/golinters/inamedparam/testdata/inamedparam_cgo.go @@ -0,0 +1,48 @@ +//golangcitest:args -Einamedparam +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type tStruct struct { + a int +} + +type Doer interface { + Do() string +} + +type NamedParam interface { + Void() + + NoArgs() string + + WithName(ctx context.Context, number int, toggle bool, tStruct *tStruct, doer Doer) (bool, error) + + WithoutName( + context.Context, // want "interface method WithoutName must have named param for type context.Context" + int, // want "interface method WithoutName must have named param for type int" + bool, // want "interface method WithoutName must have named param for type bool" + tStruct, // want "interface method WithoutName must have named param for type tStruct" + Doer, // want "interface method WithoutName must have named param for type Doer" + struct{ b bool }, // want "interface method WithoutName must have all named params" + ) +} diff --git a/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go b/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go new file mode 100644 index 000000000000..9e5b5a58fb63 --- /dev/null +++ b/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Eineffassign +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + x := math.MinInt8 + for { + _ = x + x = 0 // want "ineffectual assignment to x" + x = 0 + } +} diff --git a/pkg/golinters/interfacebloat/testdata/interfacebloat_cgo.go b/pkg/golinters/interfacebloat/testdata/interfacebloat_cgo.go new file mode 100644 index 000000000000..6359011f107b --- /dev/null +++ b/pkg/golinters/interfacebloat/testdata/interfacebloat_cgo.go @@ -0,0 +1,37 @@ +//golangcitest:args -Einterfacebloat +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type _ interface { // want "the interface has more than 10 methods: 11" + a01() time.Duration + a02() + a03() + a04() + a05() + a06() + a07() + a08() + a09() + a10() + a11() +} diff --git a/pkg/golinters/intrange/testdata/intrange_cgo.go b/pkg/golinters/intrange/testdata/intrange_cgo.go new file mode 100644 index 000000000000..af57d704fa5b --- /dev/null +++ b/pkg/golinters/intrange/testdata/intrange_cgo.go @@ -0,0 +1,40 @@ +//go:build go1.22 + +//golangcitest:args -Eintrange +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func CheckIntrange() { + for i := 0; i < 10; i++ { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` + } + + for i := uint8(0); i < math.MaxInt8; i++ { // want `for loop can be changed to use an integer range \(Go 1\.22\+\)` + } + + for i := 0; i < 10; i += 2 { + } + + for i := 0; i < 10; i++ { + i += 1 + } +} diff --git a/pkg/golinters/ireturn/testdata/ireturn_cgo.go b/pkg/golinters/ireturn/testdata/ireturn_cgo.go new file mode 100644 index 000000000000..a6f6ee3ae467 --- /dev/null +++ b/pkg/golinters/ireturn/testdata/ireturn_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Eireturn +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type ( + IreturnDoer interface{ Do() } + ireturnDoer struct{} +) + +func _() IreturnDoer { return new(ireturnDoer) } // want `_ returns interface \(command-line-arguments.IreturnDoer\)` +func (d *ireturnDoer) Do() { /*...*/ } + +func _() *ireturnDoer { return new(ireturnDoer) } diff --git a/pkg/golinters/lll/testdata/lll_cgo.go b/pkg/golinters/lll/testdata/lll_cgo.go new file mode 100644 index 000000000000..6c1ec7717959 --- /dev/null +++ b/pkg/golinters/lll/testdata/lll_cgo.go @@ -0,0 +1,26 @@ +//golangcitest:args -Elll +//golangcitest:config_path testdata/lll.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +// want +1 "The line is 137 characters long, which exceeds the maximum of 120 characters." +// In my experience, long lines are the lines with comments, not the code. So this is a long comment, a very long comment, yes very long. + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} diff --git a/pkg/golinters/loggercheck/testdata/loggercheck_cgo.go b/pkg/golinters/loggercheck/testdata/loggercheck_cgo.go new file mode 100644 index 000000000000..5f328fdea3c2 --- /dev/null +++ b/pkg/golinters/loggercheck/testdata/loggercheck_cgo.go @@ -0,0 +1,53 @@ +//golangcitest:args -Eloggercheck +package loggercheck + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "log/slog" + "unsafe" + + "github.com/go-logr/logr" + "go.uber.org/zap" + "k8s.io/klog/v2" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func ExampleDefaultLogr() { + log := logr.Discard() + log = log.WithValues("key") // want `odd number of arguments passed as key-value pairs for logging` + log.Info("message", "key1", "value1", "key2", "value2", "key3") // want `odd number of arguments passed as key-value pairs for logging` + log.Error(fmt.Errorf("error"), "message", "key1", "value1", "key2") // want `odd number of arguments passed as key-value pairs for logging` + log.Error(fmt.Errorf("error"), "message", "key1", "value1", "key2", "value2") +} + +func ExampleDefaultKlog() { + klog.InfoS("message", "key1") // want `odd number of arguments passed as key-value pairs for logging` +} + +func ExampleZapSugarNotChecked() { + sugar := zap.NewExample().Sugar() + defer sugar.Sync() + sugar.Infow("message", "key1", "value1", "key2") // want `odd number of arguments passed as key-value pairs for logging` +} + +func ExampleSlog() { + logger := slog.With("key1", "value1") + + logger.Info("msg", "key1") // want `odd number of arguments passed as key-value pairs for logging` + slog.Info("msg", "key1") // want `odd number of arguments passed as key-value pairs for logging` +} diff --git a/pkg/golinters/maintidx/testdata/maintidx_cgo.go b/pkg/golinters/maintidx/testdata/maintidx_cgo.go new file mode 100644 index 000000000000..360680253188 --- /dev/null +++ b/pkg/golinters/maintidx/testdata/maintidx_cgo.go @@ -0,0 +1,215 @@ +//golangcitest:args -Emaintidx +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { // want "Function name: _, Cyclomatic Complexity: 77, Halstead Volume: 1718.01, Maintainability Index: 17" + for true { + if false { + if false { + n := 0 + switch n { + case 0: + case 1: + case math.MaxInt: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else { + n := 0 + switch n { + case 0: + case 1: + default: + } + } + } else if false { + if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else { + n := 0 + switch n { + case 0: + case 1: + default: + } + } + } else if false { + if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else { + n := 0 + switch n { + case 0: + case 1: + default: + } + } + } else if false { + if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else { + n := 0 + switch n { + case 0: + case 1: + default: + } + } + } else { + if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else if false { + n := 0 + switch n { + case 0: + case 1: + default: + } + } else { + n := 0 + switch n { + case 0: + case 1: + default: + } + } + } + } +} diff --git a/pkg/golinters/makezero/testdata/makezero_cgo.go b/pkg/golinters/makezero/testdata/makezero_cgo.go new file mode 100644 index 000000000000..7f5b5658c8b0 --- /dev/null +++ b/pkg/golinters/makezero/testdata/makezero_cgo.go @@ -0,0 +1,28 @@ +//golangcitest:args -Emakezero +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() []int { + x := make([]int, math.MaxInt8) + return append(x, 1) // want "append to slice `x` with non-zero initialized length" +} diff --git a/pkg/golinters/mirror/testdata/mirror_cgo.go b/pkg/golinters/mirror/testdata/mirror_cgo.go new file mode 100644 index 000000000000..540810f44484 --- /dev/null +++ b/pkg/golinters/mirror/testdata/mirror_cgo.go @@ -0,0 +1,29 @@ +//golangcitest:args -Emirror +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "strings" + "unicode/utf8" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + _ = utf8.RuneCount([]byte("foobar")) // want `avoid allocations with utf8\.RuneCountInString` + _ = strings.Compare(string([]byte{'f', 'o', 'o', 'b', 'a', 'r'}), string([]byte{'f', 'o', 'o', 'b', 'a', 'r'})) // want `avoid allocations with bytes\.Compare` +} diff --git a/pkg/golinters/misspell/testdata/misspell_cgo.go b/pkg/golinters/misspell/testdata/misspell_cgo.go new file mode 100644 index 000000000000..d8b93114d93d --- /dev/null +++ b/pkg/golinters/misspell/testdata/misspell_cgo.go @@ -0,0 +1,30 @@ +//golangcitest:args -Emisspell +//golangcitest:config_path testdata/misspell.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Misspell() { + // comment with incorrect spelling: occured // want "`occured` is a misspelling of `occurred`" +} + +// the word langauge should be ignored here: it's set in config +// the word Dialogue should be ignored here: it's set in config diff --git a/pkg/golinters/mnd/testdata/mnd_cgo.go b/pkg/golinters/mnd/testdata/mnd_cgo.go new file mode 100644 index 000000000000..923c6fe75299 --- /dev/null +++ b/pkg/golinters/mnd/testdata/mnd_cgo.go @@ -0,0 +1,40 @@ +//golangcitest:args -Emnd +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "log" + "net/http" + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + c := &http.Client{ + Timeout: 2 * time.Second, // want "Magic number: 2, in detected" + } + + res, err := c.Get("https://www.google.com") + if err != nil { + log.Fatal(err) + } + defer res.Body.Close() + if res.StatusCode != 200 { // want "Magic number: 200, in detected" + log.Println("Something went wrong") + } +} diff --git a/pkg/golinters/musttag/testdata/musttag_cgo.go b/pkg/golinters/musttag/testdata/musttag_cgo.go new file mode 100644 index 000000000000..f3391301943b --- /dev/null +++ b/pkg/golinters/musttag/testdata/musttag_cgo.go @@ -0,0 +1,42 @@ +//golangcitest:args -Emusttag +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "encoding/asn1" + "encoding/json" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// builtin functions: +func _() { + var user struct { + Name string + Email string `json:"email"` + } + json.Marshal(user) // want "the given struct should be annotated with the `json` tag" +} + +// custom functions from config: +func _() { + var user struct { + Name string + Email string `asn1:"email"` + } + asn1.Marshal(user) +} diff --git a/pkg/golinters/nakedret/testdata/nakedret_cgo.go b/pkg/golinters/nakedret/testdata/nakedret_cgo.go new file mode 100644 index 000000000000..bc15c1981e45 --- /dev/null +++ b/pkg/golinters/nakedret/testdata/nakedret_cgo.go @@ -0,0 +1,58 @@ +//golangcitest:args -Enakedret +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() (a int, b string) { + if a > 0 { + return // want "naked return in func `_` with 33 lines of code" + } + + fmt.Println("nakedret") + + if b == "" { + return 0, "0" + } + + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + // ... + + // len of this function is 33 + return // want "naked return in func `_` with 33 lines of code" +} diff --git a/pkg/golinters/nestif/testdata/nestif_cgo.go b/pkg/golinters/nestif/testdata/nestif_cgo.go new file mode 100644 index 000000000000..4ffc7907d7fa --- /dev/null +++ b/pkg/golinters/nestif/testdata/nestif_cgo.go @@ -0,0 +1,70 @@ +//golangcitest:args -Enestif +//golangcitest:config_path testdata/nestif.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + fmt.Println("nestif") + + var b1, b2, b3, b4 bool + + if b1 { // want "`if b1` has complex nested blocks \\(complexity: 1\\)" + if b2 { // +1 + } + } + + if b1 { // want "`if b1` has complex nested blocks \\(complexity: 3\\)" + if b2 { // +1 + if b3 { // +2 + } + } + } + + if b1 { // want "`if b1` has complex nested blocks \\(complexity: 5\\)" + if b2 { // +1 + } else if b3 { // +1 + if b4 { // +2 + } + } else { // +1 + } + } + + if b1 { // want "`if b1` has complex nested blocks \\(complexity: 9\\)" + if b2 { // +1 + if b3 { // +2 + } + } + + if b2 { // +1 + if b3 { // +2 + if b4 { // +3 + } + } + } + } + + if b1 == b2 == b3 { // want "`if b1 == b2 == b3` has complex nested blocks \\(complexity: 1\\)" + if b4 { // +1 + } + } +} diff --git a/pkg/golinters/nilerr/testdata/nilerr_cgo.go b/pkg/golinters/nilerr/testdata/nilerr_cgo.go new file mode 100644 index 000000000000..4df2966f066c --- /dev/null +++ b/pkg/golinters/nilerr/testdata/nilerr_cgo.go @@ -0,0 +1,36 @@ +//golangcitest:args -Enilerr +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "os" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() error { + err := nilErrDo() + if err == nil { + return err // want `error is nil \(line 26\) but it returns error` + } + + return nil +} + +func nilErrDo() error { + return os.ErrNotExist +} diff --git a/pkg/golinters/nilnil/testdata/nilnil_cgo.go b/pkg/golinters/nilnil/testdata/nilnil_cgo.go new file mode 100644 index 000000000000..836be1ce7f7a --- /dev/null +++ b/pkg/golinters/nilnil/testdata/nilnil_cgo.go @@ -0,0 +1,26 @@ +//golangcitest:args -Enilnil +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() (unsafe.Pointer, error) { + return nil, nil // want "return both a `nil` error and an invalid value: use a sentinel error instead" +} diff --git a/pkg/golinters/nlreturn/testdata/nlreturn_cgo.go b/pkg/golinters/nlreturn/testdata/nlreturn_cgo.go new file mode 100644 index 000000000000..6f60af50e345 --- /dev/null +++ b/pkg/golinters/nlreturn/testdata/nlreturn_cgo.go @@ -0,0 +1,57 @@ +//golangcitest:args -Enlreturn +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func cha() { + ch := make(chan interface{}) + ch1 := make(chan interface{}) + ch2 := make(chan interface{}) + + select { + case <-ch: + return + + case <-ch1: + { + a := math.MaxInt + _ = a + { + a := 1 + _ = a + return // want "return with no blank line before" + } + + return + } + + return + + case <-ch2: + { + a := 1 + _ = a + return // want "return with no blank line before" + } + return // want "return with no blank line before" + } +} diff --git a/pkg/golinters/noctx/testdata/noctx_cgo.go b/pkg/golinters/noctx/testdata/noctx_cgo.go new file mode 100644 index 000000000000..64f5ea6c0d49 --- /dev/null +++ b/pkg/golinters/noctx/testdata/noctx_cgo.go @@ -0,0 +1,150 @@ +//golangcitest:args -Enoctx +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "net/http" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +var newRequestPkg = http.NewRequest + +func _() { + const url = "http://example.com" + cli := &http.Client{} + + ctx := context.Background() + http.Get(url) // want `net/http\.Get must not be called` + _ = http.Get // OK + f := http.Get // OK + f(url) // want `net/http\.Get must not be called` + + http.Head(url) // want `net/http\.Head must not be called` + http.Post(url, "", nil) // want `net/http\.Post must not be called` + http.PostForm(url, nil) // want `net/http\.PostForm must not be called` + + cli.Get(url) // want `\(\*net/http\.Client\)\.Get must not be called` + _ = cli.Get // OK + m := cli.Get // OK + m(url) // want `\(\*net/http\.Client\)\.Get must not be called` + + cli.Head(url) // want `\(\*net/http\.Client\)\.Head must not be called` + cli.Post(url, "", nil) // want `\(\*net/http\.Client\)\.Post must not be called` + cli.PostForm(url, nil) // want `\(\*net/http\.Client\)\.PostForm must not be called` + + req, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + cli.Do(req) + + req2, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK + cli.Do(req2) + + req3, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req3 = req3.WithContext(ctx) + cli.Do(req3) + + f2 := func(req *http.Request, ctx context.Context) *http.Request { + return req + } + req4, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req4 = f2(req4, ctx) + + req41, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req41 = req41.WithContext(ctx) + req41 = f2(req41, ctx) + + newRequest := http.NewRequest + req5, _ := newRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + cli.Do(req5) + + req51, _ := newRequest(http.MethodPost, url, nil) // OK + req51 = req51.WithContext(ctx) + cli.Do(req51) + + req52, _ := newRequestPkg(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + cli.Do(req52) + + type MyRequest = http.Request + f3 := func(req *MyRequest, ctx context.Context) *MyRequest { + return req + } + req6, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req6 = f3(req6, ctx) + + req61, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req61 = req61.WithContext(ctx) + req61 = f3(req61, ctx) + + type MyRequest2 http.Request + f4 := func(req *MyRequest2, ctx context.Context) *MyRequest2 { + return req + } + req7, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req71 := MyRequest2(*req7) + f4(&req71, ctx) + + req72, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req72 = req72.WithContext(ctx) + req73 := MyRequest2(*req7) + f4(&req73, ctx) + + req8, _ := func() (*http.Request, error) { + return http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + }() + cli.Do(req8) + + req82, _ := func() (*http.Request, error) { + req82, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req82 = req82.WithContext(ctx) + return req82, nil + }() + cli.Do(req82) + + f5 := func(req, req2 *http.Request, ctx context.Context) (*http.Request, *http.Request) { + return req, req2 + } + req9, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req9, _ = f5(req9, req9, ctx) + + req91, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req91 = req91.WithContext(ctx) + req9, _ = f5(req91, req91, ctx) + + req10, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req11, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req10, req11 = f5(req10, req11, ctx) + + req101, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req111, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req111 = req111.WithContext(ctx) + req101, req111 = f5(req101, req111, ctx) + + func() (*http.Request, *http.Request) { + req12, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req13, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + return req12, req13 + }() + + func() (*http.Request, *http.Request) { + req14, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext` + req15, _ := http.NewRequest(http.MethodPost, url, nil) // OK + req15 = req15.WithContext(ctx) + + return req14, req15 + }() +} diff --git a/pkg/golinters/nolintlint/testdata/nolintlint_cgo.go b/pkg/golinters/nolintlint/testdata/nolintlint_cgo.go new file mode 100644 index 000000000000..3bfa86b2b2ac --- /dev/null +++ b/pkg/golinters/nolintlint/testdata/nolintlint_cgo.go @@ -0,0 +1,38 @@ +//golangcitest:args -Enolintlint -Emisspell +//golangcitest:expected_linter nolintlint +//golangcitest:config_path nolintlint.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Foo() { + fmt.Println("not specific") //nolint // want "directive `.*` should mention specific linter such as `//nolint:my-linter`" + fmt.Println("not machine readable") // nolint // want "directive `.*` should be written as `//nolint`" + fmt.Println("extra spaces") // nolint:unused // because // want "directive `.*` should not have more than one leading space" + + // test expanded range + //nolint:misspell // deliberate misspelling to trigger nolintlint + func() { + mispell := true + fmt.Println(mispell) + }() +} diff --git a/pkg/golinters/nonamedreturns/testdata/nonamedreturns_cgo.go b/pkg/golinters/nonamedreturns/testdata/nonamedreturns_cgo.go new file mode 100644 index 000000000000..561878260887 --- /dev/null +++ b/pkg/golinters/nonamedreturns/testdata/nonamedreturns_cgo.go @@ -0,0 +1,31 @@ +//golangcitest:args -Enonamedreturns +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() (i int, err error) { // want `named return "i" with type "int" found` + defer func() { + i = math.MaxInt + err = nil + }() + return +} diff --git a/pkg/golinters/nosprintfhostport/testdata/nosprintfhostport_cgo.go b/pkg/golinters/nosprintfhostport/testdata/nosprintfhostport_cgo.go new file mode 100644 index 000000000000..b7548d10ec9a --- /dev/null +++ b/pkg/golinters/nosprintfhostport/testdata/nosprintfhostport_cgo.go @@ -0,0 +1,27 @@ +//golangcitest:args -Enosprintfhostport +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + _ = fmt.Sprintf("gopher://%s:%d", "myHost", 70) // want "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf" +} diff --git a/pkg/golinters/paralleltest/testdata/paralleltest_cgo.go b/pkg/golinters/paralleltest/testdata/paralleltest_cgo.go new file mode 100644 index 000000000000..e961d17bf042 --- /dev/null +++ b/pkg/golinters/paralleltest/testdata/paralleltest_cgo.go @@ -0,0 +1,25 @@ +//golangcitest:args -Eparalleltest +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "testing" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func TestFunctionMissingCallToParallel(t *testing.T) {} // want "Function TestFunctionMissingCallToParallel missing the call to method parallel" diff --git a/pkg/golinters/perfsprint/testdata/perfsprint_cgo.go b/pkg/golinters/perfsprint/testdata/perfsprint_cgo.go new file mode 100644 index 000000000000..6a029411ef0e --- /dev/null +++ b/pkg/golinters/perfsprint/testdata/perfsprint_cgo.go @@ -0,0 +1,70 @@ +//golangcitest:args -Eperfsprint +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + var ( + s string + err error + b bool + i int + i64 int64 + ui uint + ) + + fmt.Sprintf("%s", s) // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprint(s) // want "fmt.Sprint can be replaced with just using the string" + fmt.Sprintf("%s", err) + fmt.Sprint(err) + fmt.Sprintf("%t", b) // want "fmt.Sprintf can be replaced with faster strconv.FormatBool" + fmt.Sprint(b) // want "fmt.Sprint can be replaced with faster strconv.FormatBool" + fmt.Sprintf("%d", i) // want "fmt.Sprintf can be replaced with faster strconv.Itoa" + fmt.Sprint(i) // want "fmt.Sprint can be replaced with faster strconv.Itoa" + fmt.Sprintf("%d", i64) // want "fmt.Sprintf can be replaced with faster strconv.FormatInt" + fmt.Sprint(i64) // want "fmt.Sprint can be replaced with faster strconv.FormatInt" + fmt.Sprintf("%d", ui) // want "fmt.Sprintf can be replaced with faster strconv.FormatUint" + fmt.Sprint(ui) // want "fmt.Sprint can be replaced with faster strconv.FormatUint" + fmt.Sprintf("%x", []byte{'a'}) // want "fmt.Sprintf can be replaced with faster hex.EncodeToString" + fmt.Errorf("hello") // want "fmt.Errorf can be replaced with errors.New" + fmt.Sprintf("Hello %s", s) // want "fmt.Sprintf can be replaced with string concatenation" + + fmt.Sprint("test", 42) + fmt.Sprint(42, 42) + fmt.Sprintf("test") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%v") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%d") // want "fmt.Sprintf can be replaced with just using the string" + fmt.Sprintf("%d", 42, 42) + fmt.Sprintf("%#d", 42) + fmt.Sprintf("value %d", 42) + fmt.Sprintf("val%d", 42) + fmt.Sprintf("%s %v", "hello", "world") + fmt.Sprintf("%#v", 42) + fmt.Sprintf("%T", struct{ string }{}) + fmt.Sprintf("%%v", 42) + fmt.Sprintf("%3d", 42) + fmt.Sprintf("% d", 42) + fmt.Sprintf("%-10d", 42) + fmt.Sprintf("%[2]d %[1]d\n", 11, 22) + fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6) + fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) +} diff --git a/pkg/golinters/prealloc/testdata/prealloc_cgo.go b/pkg/golinters/prealloc/testdata/prealloc_cgo.go new file mode 100644 index 000000000000..0bff491ac014 --- /dev/null +++ b/pkg/golinters/prealloc/testdata/prealloc_cgo.go @@ -0,0 +1,31 @@ +//golangcitest:args -Eprealloc +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(source []int) []int { + var dest []int // want "Consider pre-allocating `dest`" + for _, v := range source { + dest = append(dest, v) + } + + return dest +} diff --git a/pkg/golinters/predeclared/testdata/predeclared_cgo.go b/pkg/golinters/predeclared/testdata/predeclared_cgo.go new file mode 100644 index 000000000000..f4e1665bfac6 --- /dev/null +++ b/pkg/golinters/predeclared/testdata/predeclared_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Epredeclared +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + var real int // want "variable real has same name as predeclared identifier" + a := A{} + copy := Clone(a) // want "variable copy has same name as predeclared identifier" + + // suppress any "declared but not used" errors + _ = real + _ = a + _ = copy +} diff --git a/pkg/golinters/promlinter/testdata/promlinter_cgo.go b/pkg/golinters/promlinter/testdata/promlinter_cgo.go new file mode 100644 index 000000000000..5eee45d0e7be --- /dev/null +++ b/pkg/golinters/promlinter/testdata/promlinter_cgo.go @@ -0,0 +1,52 @@ +//golangcitest:args -Epromlinter +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +var ( + _ = promauto.NewCounterVec( + prometheus.CounterOpts{ // want `Metric: test_metric_name Error: counter metrics should have "_total" suffix` + Name: "test_metric_name", + Help: "test help text", + }, []string{}, + ) + + _ = promauto.NewCounterVec( + prometheus.CounterOpts{ // want "Metric: test_metric_total Error: no help text" + Name: "test_metric_total", + }, []string{}, + ) + + _ = promauto.NewCounterVec( + prometheus.CounterOpts{ // want `Metric: metric_type_in_name_counter_total Error: metric name should not include type 'counter'` + Name: "metric_type_in_name_counter_total", + Help: "foo", + }, []string{}, + ) + + _ = prometheus.NewHistogram(prometheus.HistogramOpts{ // want `Metric: test_duration_milliseconds Error: use base unit "seconds" instead of "milliseconds"` + Name: "test_duration_milliseconds", + Help: "", + }) +) diff --git a/pkg/golinters/protogetter/testdata/protogetter_cgo.go b/pkg/golinters/protogetter/testdata/protogetter_cgo.go new file mode 100644 index 000000000000..a535234515c7 --- /dev/null +++ b/pkg/golinters/protogetter/testdata/protogetter_cgo.go @@ -0,0 +1,99 @@ +//golangcitest:args -Eprotogetter +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" + + "protogetter/proto" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type Test struct { + Embedded *proto.Embedded +} + +func _(t *proto.Test) { + func(...interface{}) {}(t.B, t.D) // want `avoid direct access to proto field t\.B, use t\.GetB\(\) instead` + func(...interface{}) {}(t.GetB(), t.D) // want `avoid direct access to proto field t\.D, use t\.GetD\(\) instead` + func(...interface{}) {}(t.B, t.GetD()) // want `avoid direct access to proto field t\.B, use t\.GetB\(\) instead` + + _ = t.D // want `avoid direct access to proto field t\.D, use t\.GetD\(\) instead` + _ = t.F // want `avoid direct access to proto field t\.F, use t\.GetF\(\) instead` + _ = t.I32 // want `avoid direct access to proto field t\.I32, use t\.GetI32\(\) instead` + _ = t.I64 // want `avoid direct access to proto field t\.I64, use t\.GetI64\(\) instead` + _ = t.U32 // want `avoid direct access to proto field t\.U32, use t\.GetU32\(\) instead` + _ = t.U64 // want `avoid direct access to proto field t\.U64, use t\.GetU64\(\) instead` + _ = t.T // want `avoid direct access to proto field t\.T, use t\.GetT\(\) instead` + _ = t.B // want `avoid direct access to proto field t\.B, use t\.GetB\(\) instead` + _ = t.S // want `avoid direct access to proto field t\.S, use t\.GetS\(\) instead` + _ = t.Embedded // want `avoid direct access to proto field t\.Embedded, use t\.GetEmbedded\(\) instead` + _ = t.Embedded.S // want `avoid direct access to proto field t\.Embedded\.S, use t\.GetEmbedded\(\)\.GetS\(\) instead` + _ = t.GetEmbedded().S // want `avoid direct access to proto field t\.GetEmbedded\(\)\.S, use t\.GetEmbedded\(\)\.GetS\(\) instead` + _ = t.Embedded.Embedded // want `avoid direct access to proto field t\.Embedded\.Embedded, use t\.GetEmbedded\(\)\.GetEmbedded\(\) instead` + _ = t.GetEmbedded().Embedded // want `avoid direct access to proto field t\.GetEmbedded\(\)\.Embedded, use t\.GetEmbedded\(\)\.GetEmbedded\(\) instead` + _ = t.Embedded.Embedded.S // want `avoid direct access to proto field t\.Embedded\.Embedded\.S, use t\.GetEmbedded\(\)\.GetEmbedded\(\).GetS\(\) instead` + _ = t.GetEmbedded().GetEmbedded().S // want `avoid direct access to proto field t\.GetEmbedded\(\)\.GetEmbedded\(\)\.S, use t\.GetEmbedded\(\)\.GetEmbedded\(\)\.GetS\(\) instead` + _ = t.RepeatedEmbeddeds // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = t.RepeatedEmbeddeds[0] // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = t.RepeatedEmbeddeds[0].S // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[0\]\.S, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetS\(\) instead` + _ = t.GetRepeatedEmbeddeds()[0].S // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[0\]\.S, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetS\(\) instead` + _ = t.RepeatedEmbeddeds[0].Embedded // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[0\]\.Embedded, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetEmbedded\(\) instead` + _ = t.GetRepeatedEmbeddeds()[0].Embedded // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[0\]\.Embedded, use t\.GetRepeatedEmbeddeds\(\)\[0\]\.GetEmbedded\(\) instead` + _ = t.RepeatedEmbeddeds[0].Embedded.S // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[0\]\.Embedded\.S, use t\.GetRepeatedEmbeddeds\(\)\[0\].GetEmbedded\(\).GetS\(\) instead` + _ = t.GetRepeatedEmbeddeds()[0].GetEmbedded().S // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[0\].GetEmbedded\(\).S, use t\.GetRepeatedEmbeddeds\(\)\[0\].GetEmbedded\(\).GetS\(\) instead` + _ = t.RepeatedEmbeddeds[t.I64].Embedded.S // want `avoid direct access to proto field t\.RepeatedEmbeddeds\[t.I64\]\.Embedded\.S, use t\.GetRepeatedEmbeddeds\(\)\[t\.GetI64\(\)\].GetEmbedded\(\).GetS\(\) instead` + _ = t.GetRepeatedEmbeddeds()[t.I64].GetEmbedded().S // want `avoid direct access to proto field t\.GetRepeatedEmbeddeds\(\)\[t\.I64\]\.GetEmbedded\(\)\.S, use t\.GetRepeatedEmbeddeds\(\)\[t\.GetI64\(\)\]\.GetEmbedded\(\).GetS\(\) instead` + + var many []*proto.Test + manyIndex := 42 + + _ = many[0].T // want `avoid direct access to proto field many\[0\]\.T, use many\[0\]\.GetT\(\) instead` + _ = many[1].Embedded.S // want `avoid direct access to proto field many\[1\]\.Embedded\.S, use many\[1\]\.GetEmbedded\(\)\.GetS\(\) instead` + _ = many[2].GetEmbedded().S // want `avoid direct access to proto field many\[2\]\.GetEmbedded\(\)\.S, use many\[2\].GetEmbedded\(\)\.GetS\(\) instead` + _ = many[3].Embedded.Embedded.S // want `avoid direct access to proto field many\[3\]\.Embedded\.Embedded\.S, use many\[3\].GetEmbedded\(\)\.GetEmbedded\(\)\.GetS\(\) instead` + _ = many[manyIndex].S // want `avoid direct access to proto field many\[manyIndex\]\.S, use many\[manyIndex\]\.GetS\(\) instead` + + test := many[0].Embedded.S == "" || t.Embedded.CustomMethod() == nil || t.S == "" || t.Embedded == nil // want `avoid direct access to proto field many\[0\]\.Embedded\.S, use many\[0\]\.GetEmbedded\(\).GetS\(\) instead` + _ = test + + other := proto.Other{} + _ = other.MyMethod(nil).S // want `avoid direct access to proto field other\.MyMethod\(nil\)\.S, use other\.MyMethod\(nil\)\.GetS\(\) instead` + + ems := t.RepeatedEmbeddeds // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = ems[len(ems)-1].S // want `avoid direct access to proto field ems\[len\(ems\)-1\]\.S, use ems\[len\(ems\)-1\]\.GetS\(\) instead` + + ch := make(chan string) + ch <- t.S // want `avoid direct access to proto field t\.S, use t\.GetS\(\) instead` + + for _, v := range t.RepeatedEmbeddeds { // want `avoid direct access to proto field t\.RepeatedEmbeddeds, use t\.GetRepeatedEmbeddeds\(\) instead` + _ = v + } + + fn := func(...interface{}) bool { return false } + fn((*proto.Test)(nil).S) // want `avoid direct access to proto field \(\*proto\.Test\)\(nil\)\.S, use \(\*proto\.Test\)\(nil\)\.GetS\(\) instead` + + var ptrs *[]proto.Test + _ = (*ptrs)[42].RepeatedEmbeddeds // want `avoid direct access to proto field \(\*ptrs\)\[42\]\.RepeatedEmbeddeds, use \(\*ptrs\)\[42\].GetRepeatedEmbeddeds\(\) instead` + _ = (*ptrs)[t.I64].RepeatedEmbeddeds // want `avoid direct access to proto field \(\*ptrs\)\[t\.I64\]\.RepeatedEmbeddeds, use \(\*ptrs\)\[t\.GetI64\(\)\].GetRepeatedEmbeddeds\(\) instead` + + var anyType interface{} + _ = anyType.(*proto.Test).S // want `avoid direct access to proto field anyType\.\(\*proto\.Test\)\.S, use anyType\.\(\*proto\.Test\)\.GetS\(\) instead` + + t.Embedded.SetS("test") // want `avoid direct access to proto field t\.Embedded\.SetS\("test"\), use t\.GetEmbedded\(\)\.SetS\("test"\) instead` + t.Embedded.SetMap(map[string]string{"test": "test"}) // want `avoid direct access to proto field t\.Embedded\.SetMap\(map\[string\]string{"test": "test"}\), use t\.GetEmbedded\(\)\.SetMap\(map\[string\]string{"test": "test"}\) instead` +} diff --git a/pkg/golinters/reassign/testdata/reassign_cgo.go b/pkg/golinters/reassign/testdata/reassign_cgo.go new file mode 100644 index 000000000000..9a82ecb848a2 --- /dev/null +++ b/pkg/golinters/reassign/testdata/reassign_cgo.go @@ -0,0 +1,30 @@ +//golangcitest:args -Ereassign +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "io" + "net/http" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + http.DefaultClient = nil + http.DefaultTransport = nil + io.EOF = nil // want `reassigning variable EOF in other package io` +} diff --git a/pkg/golinters/recvcheck/testdata/recvcheck_cgo.go b/pkg/golinters/recvcheck/testdata/recvcheck_cgo.go new file mode 100644 index 000000000000..0656a4c27108 --- /dev/null +++ b/pkg/golinters/recvcheck/testdata/recvcheck_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Erecvcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type Bar struct{} // want `the methods of "Bar" use pointer receiver and non-pointer receiver.` + +func (b Bar) A() { + fmt.Println("A") +} + +func (b *Bar) B() { + fmt.Println("B") +} diff --git a/pkg/golinters/revive/testdata/revive_cgo.go b/pkg/golinters/revive/testdata/revive_cgo.go new file mode 100644 index 000000000000..c6be7be3ca4a --- /dev/null +++ b/pkg/golinters/revive/testdata/revive_cgo.go @@ -0,0 +1,47 @@ +//golangcitest:args -Erevive +//golangcitest:config_path testdata/revive.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "net/http" + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(t *time.Duration) error { + if t == nil { + return nil + } else { + return nil + } +} + +func _(s string) { // want "cyclomatic: function _ has cyclomatic complexity 22" + if s == http.MethodGet || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } + + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } + + if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" { + return + } +} diff --git a/pkg/golinters/rowserrcheck/testdata/rowserrcheck_cgo.go b/pkg/golinters/rowserrcheck/testdata/rowserrcheck_cgo.go new file mode 100644 index 000000000000..d5c3db392b62 --- /dev/null +++ b/pkg/golinters/rowserrcheck/testdata/rowserrcheck_cgo.go @@ -0,0 +1,30 @@ +//golangcitest:args -Erowserrcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "database/sql" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(db *sql.DB) { + rows, _ := db.Query("select id from tb") // want "rows.Err must be checked" + for rows.Next() { + + } +} diff --git a/pkg/golinters/sloglint/testdata/sloglint_cgo.go b/pkg/golinters/sloglint/testdata/sloglint_cgo.go new file mode 100644 index 000000000000..8181b198452f --- /dev/null +++ b/pkg/golinters/sloglint/testdata/sloglint_cgo.go @@ -0,0 +1,32 @@ +//go:build go1.21 + +//golangcitest:args -Esloglint +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "log/slog" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + slog.Info("msg", "foo", 1, "bar", 2) + slog.Info("msg", slog.Int("foo", 1), slog.Int("bar", 2)) + + slog.Info("msg", "foo", 1, slog.Int("bar", 2)) // want `key-value pairs and attributes should not be mixed` +} diff --git a/pkg/golinters/spancheck/testdata/spancheck_cgo.go b/pkg/golinters/spancheck/testdata/spancheck_cgo.go new file mode 100644 index 000000000000..67a9fcd2b8a3 --- /dev/null +++ b/pkg/golinters/spancheck/testdata/spancheck_cgo.go @@ -0,0 +1,107 @@ +//golangcitest:args -Espancheck +package spancheck + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "errors" + "fmt" + "unsafe" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type testDefaultError struct{} + +func (e *testDefaultError) Error() string { + return "foo" +} + +// incorrect + +func _() { + otel.Tracer("foo").Start(context.Background(), "bar") // want "span is unassigned, probable memory leak" + ctx, _ := otel.Tracer("foo").Start(context.Background(), "bar") // want "span is unassigned, probable memory leak" + fmt.Print(ctx) +} + +func _() { + ctx, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak" + print(ctx.Done(), span.IsRecording()) +} // want "return can be reached without calling span.End" + +func _() { + var ctx, span = otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak" + print(ctx.Done(), span.IsRecording()) +} // want "return can be reached without calling span.End" + +func _() { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak" + _, span = otel.Tracer("foo").Start(context.Background(), "bar") + fmt.Print(span) + defer span.End() +} // want "return can be reached without calling span.End" + +// correct + +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") + defer span.End() + + return nil +} + +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") + defer span.End() + + if true { + return nil + } + + return nil +} + +func _() error { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") + defer span.End() + + if false { + err := errors.New("foo") + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + return err + } + + if true { + span.SetStatus(codes.Error, "foo") + span.RecordError(errors.New("foo")) + return errors.New("bar") + } + + return nil +} + +func _() { + _, span := otel.Tracer("foo").Start(context.Background(), "bar") + defer span.End() + + _, span = otel.Tracer("foo").Start(context.Background(), "bar") + defer span.End() +} diff --git a/pkg/golinters/sqlclosecheck/testdata/sqlclosecheck_cgo.go b/pkg/golinters/sqlclosecheck/testdata/sqlclosecheck_cgo.go new file mode 100644 index 000000000000..09acb0ba3e39 --- /dev/null +++ b/pkg/golinters/sqlclosecheck/testdata/sqlclosecheck_cgo.go @@ -0,0 +1,56 @@ +//golangcitest:args -Esqlclosecheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "context" + "database/sql" + "log" + "strings" + "unsafe" +) + +var ( + ctx context.Context + db *sql.DB + age = 27 + userID = 43 +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age) // want "Rows/Stmt/NamedStmt was not closed" + if err != nil { + log.Fatal(err) + } + // defer rows.Close() + + names := make([]string, 0) + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + log.Fatal(err) + } + names = append(names, name) + } + + // Check for errors from iterating over rows. + if err := rows.Err(); err != nil { + log.Fatal(err) + } + log.Printf("%s are %d years old", strings.Join(names, ", "), age) +} diff --git a/pkg/golinters/staticcheck/testdata/staticcheck_cgo.go b/pkg/golinters/staticcheck/testdata/staticcheck_cgo.go new file mode 100644 index 000000000000..71370eb9dc1a --- /dev/null +++ b/pkg/golinters/staticcheck/testdata/staticcheck_cgo.go @@ -0,0 +1,28 @@ +//golangcitest:args -Estaticcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + x := "dummy" + fmt.Printf("%d", x) // want "SA5009: Printf format %d has arg #1 of wrong type" +} diff --git a/pkg/golinters/stylecheck/testdata/stylecheck_cgo.go b/pkg/golinters/stylecheck/testdata/stylecheck_cgo.go new file mode 100644 index 000000000000..5fd796c94d14 --- /dev/null +++ b/pkg/golinters/stylecheck/testdata/stylecheck_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Estylecheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _(x int) { + switch x { + case 1: + return + default: // want "ST1015: default case should be first or last in switch statement" + return + case 2: + return + } +} diff --git a/pkg/golinters/tagalign/testdata/tagalign_cgo.go b/pkg/golinters/tagalign/testdata/tagalign_cgo.go new file mode 100644 index 000000000000..7bed1980aa58 --- /dev/null +++ b/pkg/golinters/tagalign/testdata/tagalign_cgo.go @@ -0,0 +1,34 @@ +//golangcitest:args -Etagalign +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type TagAlignExampleAlignSort struct { + Foo time.Duration `json:"foo,omitempty" yaml:"foo" xml:"foo" binding:"required" gorm:"column:foo" zip:"foo" validate:"required"` // want `binding:"required" gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"` + Bar int `validate:"required" yaml:"bar" xml:"bar" binding:"required" json:"bar,omitempty" gorm:"column:bar" zip:"bar" ` // want `binding:"required" gorm:"column:bar" json:"bar,omitempty" validate:"required" xml:"bar" yaml:"bar" zip:"bar"` + FooBar int `gorm:"column:fooBar" validate:"required" xml:"fooBar" binding:"required" json:"fooBar,omitempty" zip:"fooBar" yaml:"fooBar"` // want `binding:"required" gorm:"column:fooBar" json:"fooBar,omitempty" validate:"required" xml:"fooBar" yaml:"fooBar" zip:"fooBar"` +} + +type TagAlignExampleAlignSort2 struct { + Foo int ` xml:"foo" json:"foo,omitempty" yaml:"foo" zip:"foo" binding:"required" gorm:"column:foo" validate:"required"` // want `binding:"required" gorm:"column:foo" json:"foo,omitempty" validate:"required" xml:"foo" yaml:"foo" zip:"foo"` + Bar int `validate:"required" gorm:"column:bar" yaml:"bar" xml:"bar" binding:"required" json:"bar" zip:"bar" ` // want `binding:"required" gorm:"column:bar" json:"bar" validate:"required" xml:"bar" yaml:"bar" zip:"bar"` +} diff --git a/pkg/golinters/tagliatelle/testdata/tagliatelle_cgo.go b/pkg/golinters/tagliatelle/testdata/tagliatelle_cgo.go new file mode 100644 index 000000000000..a94c03c15d9f --- /dev/null +++ b/pkg/golinters/tagliatelle/testdata/tagliatelle_cgo.go @@ -0,0 +1,52 @@ +//golangcitest:args -Etagliatelle +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "time" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type TglFoo struct { + ID string `json:"ID"` // want `json\(camel\): got 'ID' want 'id'` + UserID string `json:"UserID"` // want `json\(camel\): got 'UserID' want 'userId'` + Name string `json:"name"` + Value time.Duration `json:"value,omitempty"` + Bar TglBar `json:"bar"` + Bur `json:"bur"` +} + +type TglBar struct { + Name string `json:"-"` + Value string `json:"value"` + CommonServiceFooItem *TglBir `json:"CommonServiceItem,omitempty"` // want `json\(camel\): got 'CommonServiceItem' want 'commonServiceItem'` +} + +type TglBir struct { + Name string `json:"-"` + Value string `json:"value"` + ReplaceAllowList []string `mapstructure:"replace-allow-list"` +} + +type Bur struct { + Name string + Value string `yaml:"Value"` // want `yaml\(camel\): got 'Value' want 'value'` + More string `json:"-"` + Also string `json:"also,omitempty"` + ReqPerS string `avro:"req_per_s"` +} diff --git a/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go b/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go new file mode 100644 index 000000000000..325e99d2b511 --- /dev/null +++ b/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go @@ -0,0 +1,42 @@ +//golangcitest:args -Etestableexamples +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Example_good() { + fmt.Println("hello") + // Output: hello +} + +func Example_goodEmptyOutput() { + fmt.Println("") + // Output: +} + +func Example_bad() { // want `^missing output for example, go test can't validate it$` + fmt.Println("hello") +} + +//nolint:testableexamples +func Example_nolint() { + fmt.Println("hello") +} diff --git a/pkg/golinters/testifylint/testdata/testifylint_cgo.go b/pkg/golinters/testifylint/testdata/testifylint_cgo.go new file mode 100644 index 000000000000..8a96b0de3e6c --- /dev/null +++ b/pkg/golinters/testifylint/testdata/testifylint_cgo.go @@ -0,0 +1,81 @@ +//golangcitest:args -Etestifylint +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "io" + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +type Bool bool + +func TestTestifylint(t *testing.T) { + var ( + predicate bool + resultInt int + resultFloat float64 + arr []string + err error + ) + + assert.Equal(t, predicate, true) // want "bool-compare: use assert\\.True" + assert.Equal(t, Bool(predicate), false) // want "bool-compare: use assert\\.False" + assert.True(t, resultInt == 1) // want "compares: use assert\\.Equal" + assert.Equal(t, len(arr), 0) // want "empty: use assert\\.Empty" + assert.Error(t, err, io.EOF) // want "error-is-as: invalid usage of assert\\.Error, use assert\\.ErrorIs instead" + assert.Nil(t, err) // want "error-nil: use assert\\.NoError" + assert.Equal(t, resultInt, 42) // want "expected-actual: need to reverse actual and expected values" + assert.Equal(t, resultFloat, 42.42) // want "float-compare: use assert\\.InEpsilon \\(or InDelta\\)" + assert.Equal(t, len(arr), 10) // want "len: use assert\\.Len" + + assert.True(t, predicate) + assert.Equal(t, resultInt, 1) // want "expected-actual: need to reverse actual and expected values" + assert.Empty(t, arr) + assert.ErrorIs(t, err, io.EOF) // want "require-error: for error assertions use require" + assert.NoError(t, err) // want "require-error: for error assertions use require" + assert.Equal(t, 42, resultInt) + assert.InEpsilon(t, 42.42, resultFloat, 0.0001) + assert.Len(t, arr, 10) + + require.ErrorIs(t, err, io.EOF) + require.NoError(t, err) + + t.Run("formatted", func(t *testing.T) { + assert.Equal(t, predicate, true, "message") // want "bool-compare: use assert\\.True" + assert.Equal(t, predicate, true, "message %d", 42) // want "bool-compare: use assert\\.True" + assert.Equalf(t, predicate, true, "message") // want "bool-compare: use assert\\.Truef" + assert.Equalf(t, predicate, true, "message %d", 42) // want "bool-compare: use assert\\.Truef" + + assert.Equal(t, 1, 2, fmt.Sprintf("msg")) // want "formatter: remove unnecessary fmt\\.Sprintf" + assert.Equalf(t, 1, 2, "msg with arg", "arg") // want "formatter: assert\\.Equalf call has arguments but no formatting directives" + }) + + assert.Equal(t, arr, nil) // want "nil-compare: use assert\\.Nil" + assert.Nil(t, arr) + + go func() { + if assert.Error(t, err) { + require.ErrorIs(t, err, io.EOF) // want "go-require: require must only be used in the goroutine running the test function" + } + }() +} diff --git a/pkg/golinters/thelper/testdata/thelper_cgo.go b/pkg/golinters/thelper/testdata/thelper_cgo.go new file mode 100644 index 000000000000..c97390a88e1a --- /dev/null +++ b/pkg/golinters/thelper/testdata/thelper_cgo.go @@ -0,0 +1,28 @@ +//golangcitest:args -Ethelper +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "testing" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func thelperWithHelperAfterAssignment(t *testing.T) { // want "test helper function should start from t.Helper()" + _ = 0 + t.Helper() +} diff --git a/pkg/golinters/tparallel/testdata/tparallel_cgo.go b/pkg/golinters/tparallel/testdata/tparallel_cgo.go new file mode 100644 index 000000000000..0cb2a3d57522 --- /dev/null +++ b/pkg/golinters/tparallel/testdata/tparallel_cgo.go @@ -0,0 +1,30 @@ +//golangcitest:args -Etparallel +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "testing" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func TestSubtests(t *testing.T) { // want "TestSubtests's subtests should call t.Parallel" + t.Parallel() + + t.Run("", func(t *testing.T) { + }) +} diff --git a/pkg/golinters/unconvert/testdata/unconvert_cgo.go b/pkg/golinters/unconvert/testdata/unconvert_cgo.go new file mode 100644 index 000000000000..176b2d876b29 --- /dev/null +++ b/pkg/golinters/unconvert/testdata/unconvert_cgo.go @@ -0,0 +1,136 @@ +//golangcitest:args -Eunconvert +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +// Various explicit conversions of untyped constants +// that cannot be removed. +func _() { + const ( + _ = byte(0) + _ = int((real)(0i)) + _ = complex64(complex(1, 2)) + _ = (bool)(true || false) + + PtrSize = 4 << (^uintptr(0) >> 63) + c0 = uintptr(PtrSize) + c1 = uintptr((8-PtrSize)/4*2860486313 + (PtrSize-4)/4*33054211828000289) + ) + + i := int64(0) + _ = i +} + +// Make sure we distinguish function calls from +// conversion to function type. +func _() { + type F func(F) int + var f F + + _ = F(F(nil)) // want "unnecessary conversion" + _ = f(F(nil)) +} + +// Make sure we don't remove explicit conversions that +// prevent fusing floating-point operation. +func _() { + var f1, f2, f3, ftmp float64 + _ = f1 + float64(f2*f3) + ftmp = float64(f2 * f3) + _ = f1 + ftmp + ftmp = f2 * f3 + _ = f1 + float64(ftmp) + + var c1, c2, c3, ctmp complex128 + _ = c1 + complex128(c2*c3) + ctmp = complex128(c2 * c3) + _ = c1 + ctmp + ctmp = c2 * c3 + _ = c1 + complex128(ctmp) +} + +// Basic contains conversion errors for builtin data types +func Basic() { + var vbool bool + var vbyte byte + var vcomplex128 complex128 + var vcomplex64 complex64 + var verror error + var vfloat32 float32 + var vfloat64 float64 + var vint int + var vint16 int16 + var vint32 int32 + var vint64 int64 + var vint8 int8 + var vrune rune + var vstring string + var vuint uint + var vuint16 uint16 + var vuint32 uint32 + var vuint64 uint64 + var vuint8 uint8 + var vuintptr uintptr + + _ = bool(vbool) // want "unnecessary conversion" + _ = byte(vbyte) // want "unnecessary conversion" + _ = error(verror) // want "unnecessary conversion" + _ = int(vint) // want "unnecessary conversion" + _ = int16(vint16) // want "unnecessary conversion" + _ = int32(vint32) // want "unnecessary conversion" + _ = int64(vint64) // want "unnecessary conversion" + _ = int8(vint8) // want "unnecessary conversion" + _ = rune(vrune) // want "unnecessary conversion" + _ = string(vstring) // want "unnecessary conversion" + _ = uint(vuint) // want "unnecessary conversion" + _ = uint16(vuint16) // want "unnecessary conversion" + _ = uint32(vuint32) // want "unnecessary conversion" + _ = uint64(vuint64) // want "unnecessary conversion" + _ = uint8(vuint8) // want "unnecessary conversion" + _ = uintptr(vuintptr) // want "unnecessary conversion" + + _ = float32(vfloat32) + _ = float64(vfloat64) + _ = complex128(vcomplex128) + _ = complex64(vcomplex64) + + // Pointers + _ = (*bool)(&vbool) // want "unnecessary conversion" + _ = (*byte)(&vbyte) // want "unnecessary conversion" + _ = (*complex128)(&vcomplex128) // want "unnecessary conversion" + _ = (*complex64)(&vcomplex64) // want "unnecessary conversion" + _ = (*error)(&verror) // want "unnecessary conversion" + _ = (*float32)(&vfloat32) // want "unnecessary conversion" + _ = (*float64)(&vfloat64) // want "unnecessary conversion" + _ = (*int)(&vint) // want "unnecessary conversion" + _ = (*int16)(&vint16) // want "unnecessary conversion" + _ = (*int32)(&vint32) // want "unnecessary conversion" + _ = (*int64)(&vint64) // want "unnecessary conversion" + _ = (*int8)(&vint8) // want "unnecessary conversion" + _ = (*rune)(&vrune) // want "unnecessary conversion" + _ = (*string)(&vstring) // want "unnecessary conversion" + _ = (*uint)(&vuint) // want "unnecessary conversion" + _ = (*uint16)(&vuint16) // want "unnecessary conversion" + _ = (*uint32)(&vuint32) // want "unnecessary conversion" + _ = (*uint64)(&vuint64) // want "unnecessary conversion" + _ = (*uint8)(&vuint8) // want "unnecessary conversion" + _ = (*uintptr)(&vuintptr) // want "unnecessary conversion" +} diff --git a/pkg/golinters/unparam/testdata/unparam_cgo.go b/pkg/golinters/unparam/testdata/unparam_cgo.go new file mode 100644 index 000000000000..5a4967157ae1 --- /dev/null +++ b/pkg/golinters/unparam/testdata/unparam_cgo.go @@ -0,0 +1,27 @@ +//golangcitest:args -Eunparam +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func unparamUnusedCGO(a, b uint) uint { // want "`unparamUnusedCGO` - `b` is unused" + a++ + return a +} diff --git a/pkg/golinters/unused/testdata/unused_cgo.go b/pkg/golinters/unused/testdata/unused_cgo.go new file mode 100644 index 000000000000..769fdc88ba4d --- /dev/null +++ b/pkg/golinters/unused/testdata/unused_cgo.go @@ -0,0 +1,24 @@ +//golangcitest:args -Eunused +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func fn1() {} // want "func `fn1` is unused" diff --git a/pkg/golinters/usestdlibvars/testdata/usestdlibvars_cgo.go b/pkg/golinters/usestdlibvars/testdata/usestdlibvars_cgo.go new file mode 100644 index 000000000000..701db3cd6be2 --- /dev/null +++ b/pkg/golinters/usestdlibvars/testdata/usestdlibvars_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Eusestdlibvars +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "net/http" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _200() { + _ = 200 +} + +func _200_1() { + var w http.ResponseWriter + w.WriteHeader(200) // want `"200" can be replaced by http.StatusOK` +} diff --git a/pkg/golinters/usetesting/testdata/usetesting_cgo.go b/pkg/golinters/usetesting/testdata/usetesting_cgo.go new file mode 100644 index 000000000000..1af0b0907cad --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting_cgo.go @@ -0,0 +1,28 @@ +//golangcitest:args -Eusetesting +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "os" + "testing" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func Test_osMkdirTemp(t *testing.T) { + os.MkdirTemp("", "") // want `os\.MkdirTemp\(\) could be replaced by t\.TempDir\(\) in .+` +} diff --git a/pkg/golinters/varnamelen/testdata/varnamelen_cgo.go b/pkg/golinters/varnamelen/testdata/varnamelen_cgo.go new file mode 100644 index 000000000000..efbb6ca23905 --- /dev/null +++ b/pkg/golinters/varnamelen/testdata/varnamelen_cgo.go @@ -0,0 +1,36 @@ +//golangcitest:args -Evarnamelen +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "math" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + x := math.MinInt8 // want "variable name 'x' is too short for the scope of its usage" + x++ + x++ + x++ + x++ + x++ + x++ + x++ + x++ + x++ +} diff --git a/pkg/golinters/wastedassign/testdata/wastedassign_cgo.go b/pkg/golinters/wastedassign/testdata/wastedassign_cgo.go new file mode 100644 index 000000000000..81c872840d55 --- /dev/null +++ b/pkg/golinters/wastedassign/testdata/wastedassign_cgo.go @@ -0,0 +1,185 @@ +//golangcitest:args -Ewastedassign +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "strings" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func pa(x int) int { + return x + 1 +} + +func multiple(val interface{}, times uint) interface{} { + + switch hogehoge := val.(type) { + case int: + return 12 + case string: + return strings.Repeat(hogehoge, int(times)) + default: + return nil + } +} + +func noUseParams(params string) int { + a := 12 + println(a) + return a +} + +func f(param int) int { + println(param) + useOutOfIf := 1212121 // want "assigned to useOutOfIf, but reassigned without using the value" + ret := 0 + if false { + useOutOfIf = 200 // want "assigned to useOutOfIf, but never used afterwards" + return 0 + } else if param == 100 { + useOutOfIf = 100 // want "assigned to useOutOfIf, but reassigned without using the value" + useOutOfIf = 201 + useOutOfIf = pa(useOutOfIf) + useOutOfIf += 200 // want "assigned to useOutOfIf, but reassigned without using the value" + } else { + useOutOfIf = 100 + useOutOfIf += 100 + useOutOfIf = pa(useOutOfIf) + useOutOfIf += 200 // want "assigned to useOutOfIf, but reassigned without using the value" + } + + if false { + useOutOfIf = 200 // want "assigned to useOutOfIf, but never used afterwards" + return 0 + } else if param == 200 { + useOutOfIf = 100 // want "assigned to useOutOfIf, but reassigned without using the value" + useOutOfIf = 201 + useOutOfIf = pa(useOutOfIf) + useOutOfIf += 200 + } else { + useOutOfIf = 100 + useOutOfIf += 100 + useOutOfIf = pa(useOutOfIf) + useOutOfIf += 200 + } + // useOutOfIf = 12 + println(useOutOfIf) + useOutOfIf = 192 + useOutOfIf += 100 + useOutOfIf += 200 // want "assigned to useOutOfIf, but never used afterwards" + return ret +} + +func checkLoopTest() int { + hoge := 12 + noUse := 1111 + println(noUse) + + noUse = 1111 // want "assigned to noUse, but never used afterwards" + for { + if hoge == 14 { + break + } + hoge = hoge + 1 + } + return hoge +} + +func r(param int) int { + println(param) + useOutOfIf := 1212121 + ret := 0 + if false { + useOutOfIf = 200 // want "assigned to useOutOfIf, but never used afterwards" + return 0 + } else if param == 100 { + ret = useOutOfIf + } else if param == 200 { + useOutOfIf = 100 // want "assigned to useOutOfIf, but reassigned without using the value" + useOutOfIf = 100 + useOutOfIf = pa(useOutOfIf) + useOutOfIf += 200 // want "assigned to useOutOfIf, but reassigned without using the value" + } + useOutOfIf = 12 + println(useOutOfIf) + useOutOfIf = 192 + useOutOfIf += 100 + useOutOfIf += 200 // want "assigned to useOutOfIf, but never used afterwards" + return ret +} + +func mugen() { + var i int + var hoge int + for { + hoge = 5 // want "assigned to hoge, but reassigned without using the value" + // break + } + + println(i) + println(hoge) + return +} + +func mugenG[T ~int](hoge T) { + var i int + for { + hoge = 5 // want "assigned to hoge, but reassigned without using the value" + // break + } + + println(i) + println(hoge) + return +} + +func noMugen() { + var i int + var hoge int + for { + hoge = 5 + break + } + + println(i) + println(hoge) + return +} + +func reassignInsideLoop() { + bar := func(b []byte) ([]byte, error) { return b, nil } + var err error + var rest []byte + for { + rest, err = bar(rest) + if err == nil { + break + } + } + return +} + +func reassignInsideLoop2() { + var x int = 0 + var y int = 1 + for i := 1; i < 3; i++ { + x += y + y *= 2 * i + } + println(x) +} diff --git a/pkg/golinters/whitespace/testdata/whitespace_cgo.go b/pkg/golinters/whitespace/testdata/whitespace_cgo.go new file mode 100644 index 000000000000..58305eee4697 --- /dev/null +++ b/pkg/golinters/whitespace/testdata/whitespace_cgo.go @@ -0,0 +1,102 @@ +//golangcitest:args -Ewhitespace +//golangcitest:config_path testdata/whitespace.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "strings" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func oneLeadingNewline() { + + fmt.Println("Hello world") +} + +func oneNewlineAtBothEnds() { + + fmt.Println("Hello world") + +} + +func noNewlineFunc() { +} + +func oneNewlineFunc() { + +} + +func twoNewlinesFunc() { + + +} + +func noNewlineWithCommentFunc() { + // some comment +} + +func oneTrailingNewlineWithCommentFunc() { + // some comment + +} + +func oneLeadingNewlineWithCommentFunc() { + + // some comment +} + +func twoLeadingNewlines() { + + + fmt.Println("Hello world") +} + +func multiFuncFunc(a int, + b int) { + fmt.Println("Hello world") +} + +func multiIfFunc() { + if 1 == 1 && + 2 == 2 { + fmt.Println("Hello multi-line world") + } + + if true { + if true { + if true { + if 1 == 1 && + 2 == 2 { + fmt.Println("Hello nested multi-line world") + } + } + } + } +} + +func notGoFmted() { + + + + + fmt.Println("Hello world") + + + +} diff --git a/pkg/golinters/wrapcheck/testdata/wrapcheck_cgo.go b/pkg/golinters/wrapcheck/testdata/wrapcheck_cgo.go new file mode 100644 index 000000000000..5a587392be1a --- /dev/null +++ b/pkg/golinters/wrapcheck/testdata/wrapcheck_cgo.go @@ -0,0 +1,32 @@ +//golangcitest:args -Ewrapcheck +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "encoding/json" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() error { + _, err := json.Marshal(struct{}{}) + if err != nil { + return err // want "error returned from external package is unwrapped" + } + + return nil +} diff --git a/pkg/golinters/wsl/testdata/wsl_cgo.go b/pkg/golinters/wsl/testdata/wsl_cgo.go new file mode 100644 index 000000000000..c391f5b9a443 --- /dev/null +++ b/pkg/golinters/wsl/testdata/wsl_cgo.go @@ -0,0 +1,33 @@ +//golangcitest:args -Ewsl +//golangcitest:config_path testdata/wsl.yml +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + var ( + y = 0 + ) + if y < 1 { // want "if statements should only be cuddled with assignments" + fmt.Println("tight") + } +} diff --git a/pkg/golinters/zerologlint/testdata/zerologlint_cgo.go b/pkg/golinters/zerologlint/testdata/zerologlint_cgo.go new file mode 100644 index 000000000000..45e273067875 --- /dev/null +++ b/pkg/golinters/zerologlint/testdata/zerologlint_cgo.go @@ -0,0 +1,56 @@ +//golangcitest:args -Ezerologlint +package testdata + +/* + #include + #include + + void myprint(char* s) { + printf("%d\n", s); + } +*/ +import "C" + +import ( + "fmt" + "unsafe" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func _() { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + log.Error() // want "must be dispatched by Msg or Send method" + log.Info() // want "must be dispatched by Msg or Send method" + log.Fatal() // want "must be dispatched by Msg or Send method" + log.Debug() // want "must be dispatched by Msg or Send method" + log.Warn() // want "must be dispatched by Msg or Send method" + + err := fmt.Errorf("foobarerror") + log.Error().Err(err) // want "must be dispatched by Msg or Send method" + log.Error().Err(err).Str("foo", "bar").Int("foo", 1) // want "must be dispatched by Msg or Send method" + + logger := log.Error() // want "must be dispatched by Msg or Send method" + logger.Err(err).Str("foo", "bar").Int("foo", 1) + + // include zerolog.Dict() + log.Info(). // want "must be dispatched by Msg or Send method" + Str("foo", "bar"). + Dict("dict", zerolog.Dict(). + Str("bar", "baz"). + Int("n", 1), + ) + + // conditional + logger2 := log.Info() // want "must be dispatched by Msg or Send method" + if err != nil { + logger2 = log.Error() // want "must be dispatched by Msg or Send method" + } + logger2.Str("foo", "bar") +} From 225f0f96f80e83f7d866b919d4d2cd91e1b13666 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 02:22:02 +0100 Subject: [PATCH 51/57] tests: ignore cgo tests for some linters --- pkg/golinters/exhaustive/testdata/exhaustive_cgo.go | 4 ++++ pkg/golinters/gci/testdata/gci_cgo.go | 4 ++++ pkg/golinters/godot/testdata/godot_cgo.go | 4 ++++ pkg/golinters/goheader/testdata/goheader_cgo.go | 4 ++++ pkg/golinters/gosec/testdata/gosec_cgo.go | 4 ++++ pkg/golinters/ineffassign/testdata/ineffassign_cgo.go | 4 ++++ .../testableexamples/testdata/testableexamples_test_cgo.go | 4 ++++ pkg/golinters/unparam/testdata/unparam_cgo.go | 4 ++++ pkg/golinters/unused/testdata/unused_cgo.go | 4 ++++ pkg/golinters/wsl/testdata/wsl_cgo.go | 4 ++++ 10 files changed, 40 insertions(+) diff --git a/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go b/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go index 4cac543fba4c..bead8591ff51 100644 --- a/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go +++ b/pkg/golinters/exhaustive/testdata/exhaustive_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Eexhaustive package testdata diff --git a/pkg/golinters/gci/testdata/gci_cgo.go b/pkg/golinters/gci/testdata/gci_cgo.go index 8fe72c42e0e5..15a1735832f6 100644 --- a/pkg/golinters/gci/testdata/gci_cgo.go +++ b/pkg/golinters/gci/testdata/gci_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Egci //golangcitest:config_path testdata/gci.yml package testdata diff --git a/pkg/golinters/godot/testdata/godot_cgo.go b/pkg/golinters/godot/testdata/godot_cgo.go index 89f674fc18e3..ee12dcd94f0b 100644 --- a/pkg/golinters/godot/testdata/godot_cgo.go +++ b/pkg/golinters/godot/testdata/godot_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Egodot package testdata diff --git a/pkg/golinters/goheader/testdata/goheader_cgo.go b/pkg/golinters/goheader/testdata/goheader_cgo.go index 0727260f0092..7b668fd34f24 100644 --- a/pkg/golinters/goheader/testdata/goheader_cgo.go +++ b/pkg/golinters/goheader/testdata/goheader_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + /*MY TITLE!*/ // want `Expected:TITLE\., Actual: TITLE!` //golangcitest:args -Egoheader diff --git a/pkg/golinters/gosec/testdata/gosec_cgo.go b/pkg/golinters/gosec/testdata/gosec_cgo.go index 7b049892a7b8..84af323fb7a4 100644 --- a/pkg/golinters/gosec/testdata/gosec_cgo.go +++ b/pkg/golinters/gosec/testdata/gosec_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Egosec package testdata diff --git a/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go b/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go index 9e5b5a58fb63..600e5dc05658 100644 --- a/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go +++ b/pkg/golinters/ineffassign/testdata/ineffassign_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Eineffassign package testdata diff --git a/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go b/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go index 325e99d2b511..16d521c6ff4e 100644 --- a/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go +++ b/pkg/golinters/testableexamples/testdata/testableexamples_test_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Etestableexamples package testdata diff --git a/pkg/golinters/unparam/testdata/unparam_cgo.go b/pkg/golinters/unparam/testdata/unparam_cgo.go index 5a4967157ae1..5cc81e0958b5 100644 --- a/pkg/golinters/unparam/testdata/unparam_cgo.go +++ b/pkg/golinters/unparam/testdata/unparam_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Eunparam package testdata diff --git a/pkg/golinters/unused/testdata/unused_cgo.go b/pkg/golinters/unused/testdata/unused_cgo.go index 769fdc88ba4d..6a8becf4d895 100644 --- a/pkg/golinters/unused/testdata/unused_cgo.go +++ b/pkg/golinters/unused/testdata/unused_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Eunused package testdata diff --git a/pkg/golinters/wsl/testdata/wsl_cgo.go b/pkg/golinters/wsl/testdata/wsl_cgo.go index c391f5b9a443..aa4870e2f19d 100644 --- a/pkg/golinters/wsl/testdata/wsl_cgo.go +++ b/pkg/golinters/wsl/testdata/wsl_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Ewsl //golangcitest:config_path testdata/wsl.yml package testdata From 94073d2c6c23b1640c0a6f6f5a14bfe4cf0cfe26 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 16 Dec 2024 15:46:54 +0100 Subject: [PATCH 52/57] fix: dupl on Windows --- pkg/golinters/dupl/dupl.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/golinters/dupl/dupl.go b/pkg/golinters/dupl/dupl.go index 7abcb4c4f4de..b11a0b8e5663 100644 --- a/pkg/golinters/dupl/dupl.go +++ b/pkg/golinters/dupl/dupl.go @@ -3,6 +3,7 @@ package dupl import ( "fmt" "go/token" + "strings" "sync" duplAPI "github.com/golangci/dupl" @@ -56,7 +57,17 @@ func New(settings *config.DuplSettings) *goanalysis.Linter { func runDupl(pass *analysis.Pass, settings *config.DuplSettings) ([]goanalysis.Issue, error) { fileNames := internal.GetFileNames(pass) - issues, err := duplAPI.Run(fileNames, settings.Threshold) + var onlyGofiles []string + for _, name := range fileNames { + // Related to Windows + if !strings.HasSuffix(name, ".go") { + continue + } + + onlyGofiles = append(onlyGofiles, name) + } + + issues, err := duplAPI.Run(onlyGofiles, settings.Threshold) if err != nil { return nil, err } From 3763e1aea640914701a76e2deb7a36fd8549c745 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 17 Dec 2024 00:12:57 +0100 Subject: [PATCH 53/57] review --- .../testdata/{asasalint_cgo.go => asciicheck_cgo.go} | 0 pkg/golinters/internal/diff.go | 9 +++++++-- pkg/golinters/nolintlint/internal/nolintlint.go | 4 ++-- pkg/golinters/nolintlint/nolintlint.go | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) rename pkg/golinters/asciicheck/testdata/{asasalint_cgo.go => asciicheck_cgo.go} (100%) diff --git a/pkg/golinters/asciicheck/testdata/asasalint_cgo.go b/pkg/golinters/asciicheck/testdata/asciicheck_cgo.go similarity index 100% rename from pkg/golinters/asciicheck/testdata/asasalint_cgo.go rename to pkg/golinters/asciicheck/testdata/asciicheck_cgo.go diff --git a/pkg/golinters/internal/diff.go b/pkg/golinters/internal/diff.go index eafb88789295..c9630de0cb7d 100644 --- a/pkg/golinters/internal/diff.go +++ b/pkg/golinters/internal/diff.go @@ -214,8 +214,13 @@ func parseDiffLines(h *diffpkg.Hunk) []diffLine { return diffLines } -func ExtractDiagnosticFromPatch(pass *analysis.Pass, file *ast.File, patch string, - lintCtx *linter.Context, formatter fmtTextFormatter) error { +func ExtractDiagnosticFromPatch( + pass *analysis.Pass, + file *ast.File, + patch string, + lintCtx *linter.Context, + formatter fmtTextFormatter, +) error { diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch)) if err != nil { return fmt.Errorf("can't parse patch: %w", err) diff --git a/pkg/golinters/nolintlint/internal/nolintlint.go b/pkg/golinters/nolintlint/internal/nolintlint.go index b21bc2b52463..21cd20124f83 100644 --- a/pkg/golinters/nolintlint/internal/nolintlint.go +++ b/pkg/golinters/nolintlint/internal/nolintlint.go @@ -134,7 +134,7 @@ func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { if lintersText != "" && !strings.HasPrefix(lintersText, "all") { lls := strings.Split(lintersText, ",") linters = make([]string, 0, len(lls)) - rangeStart := (pos.Column - 1) + len("//") + len(leadingSpace) + len("nolint:") + rangeStart := (pos.Column - 1) + len(commentMark) + len(leadingSpace) + len("nolint:") for i, ll := range lls { rangeEnd := rangeStart + len(ll) if i < len(lls)-1 { @@ -202,7 +202,7 @@ func (l Linter) Run(pass *analysis.Pass) ([]goanalysis.Issue, error) { } } - if (l.needs&NeedsExplanation) != 0 && (explanation == "" || strings.TrimSpace(explanation) == "//") { + if (l.needs&NeedsExplanation) != 0 && (explanation == "" || strings.TrimSpace(explanation) == commentMark) { needsExplanation := len(linters) == 0 // if no linters are mentioned, we must have explanation // otherwise, check if we are excluding all the mentioned linters for _, ll := range linters { diff --git a/pkg/golinters/nolintlint/nolintlint.go b/pkg/golinters/nolintlint/nolintlint.go index 75933f7c3c52..e1c878628d61 100644 --- a/pkg/golinters/nolintlint/nolintlint.go +++ b/pkg/golinters/nolintlint/nolintlint.go @@ -33,7 +33,7 @@ func New(settings *config.NoLintLintSettings) *goanalysis.Linter { lnt, err := nolintlint.NewLinter(needs, settings.AllowNoExplanation) if err != nil { - internal.LinterLogger.Fatalf("asasalint: create analyzer: %v", err) + internal.LinterLogger.Fatalf("%s: create analyzer: %v", nolintlint.LinterName, err) } analyzer := &analysis.Analyzer{ From f4b2e7a10fee3f515cec39e4488013df0106fffd Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 17 Dec 2024 00:52:13 +0100 Subject: [PATCH 54/57] tests: add missing whitespace tests --- .../whitespace/testdata/whitespace.go | 18 +++++++-------- .../whitespace/testdata/whitespace_cgo.go | 23 +++++++++++-------- .../whitespace/whitespace_integration_test.go | 4 ++++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pkg/golinters/whitespace/testdata/whitespace.go b/pkg/golinters/whitespace/testdata/whitespace.go index 1c9da1edd6c9..c482c94f9e18 100644 --- a/pkg/golinters/whitespace/testdata/whitespace.go +++ b/pkg/golinters/whitespace/testdata/whitespace.go @@ -4,16 +4,16 @@ package testdata import "fmt" -func oneLeadingNewline() { +func oneLeadingNewline() { // want "unnecessary leading newline" fmt.Println("Hello world") } -func oneNewlineAtBothEnds() { +func oneNewlineAtBothEnds() { // want "unnecessary leading newline" fmt.Println("Hello world") -} +} // want "unnecessary trailing newline" func noNewlineFunc() { } @@ -41,20 +41,20 @@ func oneLeadingNewlineWithCommentFunc() { // some comment } -func twoLeadingNewlines() { +func twoLeadingNewlines() { // want "unnecessary leading newline" fmt.Println("Hello world") } func multiFuncFunc(a int, - b int) { + b int) { // want "multi-line statement should be followed by a newline" fmt.Println("Hello world") } func multiIfFunc() { if 1 == 1 && - 2 == 2 { + 2 == 2 { // want "multi-line statement should be followed by a newline" fmt.Println("Hello multi-line world") } @@ -62,7 +62,7 @@ func multiIfFunc() { if true { if true { if 1 == 1 && - 2 == 2 { + 2 == 2 { // want "multi-line statement should be followed by a newline" fmt.Println("Hello nested multi-line world") } } @@ -70,7 +70,7 @@ func multiIfFunc() { } } -func notGoFmted() { +func notGoFmted() { // want "unnecessary leading newline" @@ -79,4 +79,4 @@ func notGoFmted() { -} +} // want "unnecessary trailing newline" diff --git a/pkg/golinters/whitespace/testdata/whitespace_cgo.go b/pkg/golinters/whitespace/testdata/whitespace_cgo.go index 58305eee4697..dbc2b10e0cf7 100644 --- a/pkg/golinters/whitespace/testdata/whitespace_cgo.go +++ b/pkg/golinters/whitespace/testdata/whitespace_cgo.go @@ -1,3 +1,7 @@ +//go:build ignore + +// TODO(ldez) the linter doesn't support cgo. + //golangcitest:args -Ewhitespace //golangcitest:config_path testdata/whitespace.yml package testdata @@ -14,7 +18,6 @@ import "C" import ( "fmt" - "strings" "unsafe" ) @@ -24,16 +27,16 @@ func _() { C.free(unsafe.Pointer(cs)) } -func oneLeadingNewline() { +func oneLeadingNewline() { // want "unnecessary leading newline" fmt.Println("Hello world") } -func oneNewlineAtBothEnds() { +func oneNewlineAtBothEnds() { // want "unnecessary leading newline" fmt.Println("Hello world") -} +} // want "unnecessary trailing newline" func noNewlineFunc() { } @@ -61,20 +64,20 @@ func oneLeadingNewlineWithCommentFunc() { // some comment } -func twoLeadingNewlines() { +func twoLeadingNewlines() { // want "unnecessary leading newline" fmt.Println("Hello world") } func multiFuncFunc(a int, - b int) { + b int) { // want "multi-line statement should be followed by a newline" fmt.Println("Hello world") } func multiIfFunc() { if 1 == 1 && - 2 == 2 { + 2 == 2 { // want "multi-line statement should be followed by a newline" fmt.Println("Hello multi-line world") } @@ -82,7 +85,7 @@ func multiIfFunc() { if true { if true { if 1 == 1 && - 2 == 2 { + 2 == 2 { // want "multi-line statement should be followed by a newline" fmt.Println("Hello nested multi-line world") } } @@ -90,7 +93,7 @@ func multiIfFunc() { } } -func notGoFmted() { +func notGoFmted() { // want "unnecessary leading newline" @@ -99,4 +102,4 @@ func notGoFmted() { -} +} // want "unnecessary trailing newline" diff --git a/pkg/golinters/whitespace/whitespace_integration_test.go b/pkg/golinters/whitespace/whitespace_integration_test.go index fd80dafbb651..015dee5e9d26 100644 --- a/pkg/golinters/whitespace/whitespace_integration_test.go +++ b/pkg/golinters/whitespace/whitespace_integration_test.go @@ -6,6 +6,10 @@ import ( "github.com/golangci/golangci-lint/test/testshared/integration" ) +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} + func TestFix(t *testing.T) { integration.RunFix(t) } From 898a5dac85b5a6bbab35462f5627c15581a4b5e3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 17 Dec 2024 01:47:23 +0100 Subject: [PATCH 55/57] chore: add comments about TextEdits positions adjustements --- pkg/goanalysis/runners.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/goanalysis/runners.go b/pkg/goanalysis/runners.go index 644b9b542242..d71fccb7876f 100644 --- a/pkg/goanalysis/runners.go +++ b/pkg/goanalysis/runners.go @@ -106,6 +106,8 @@ func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) st end = edit.Pos } + // To be applied the positions need to be "adjusted" based on the file. + // This is the difference between the "displayed" positions and "effective" positions. nsf.TextEdits = append(nsf.TextEdits, analysis.TextEdit{ Pos: token.Pos(diag.File.Offset(edit.Pos)), End: token.Pos(diag.File.Offset(end)), From 63b900158c81c4d9e69eb11aca23acebde5887a7 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 17 Dec 2024 17:43:18 +0100 Subject: [PATCH 56/57] feat: improve error handling --- pkg/result/processors/fixer.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/result/processors/fixer.go b/pkg/result/processors/fixer.go index 084655f7758e..ccab513cfea7 100644 --- a/pkg/result/processors/fixer.go +++ b/pkg/result/processors/fixer.go @@ -59,7 +59,7 @@ func (p Fixer) Process(issues []result.Issue) ([]result.Issue, error) { return p.process(issues) }) if err != nil { - p.log.Errorf("Failed to fix issues: %v", err) + p.log.Warnf("Failed to fix issues: %v", err) } p.printStat() @@ -167,13 +167,13 @@ func (p Fixer) process(issues []result.Issue) ([]result.Issue, error) { for path, edits := range editsByPath { contents, err := p.fileCache.GetFileBytes(path) if err != nil { - editError = errors.Join(editError, err) + editError = errors.Join(editError, fmt.Errorf("%s: %w", path, err)) continue } out, err := diff.ApplyBytes(contents, edits) if err != nil { - editError = errors.Join(editError, err) + editError = errors.Join(editError, fmt.Errorf("%s: %w", path, err)) continue } @@ -183,7 +183,7 @@ func (p Fixer) process(issues []result.Issue) ([]result.Issue, error) { } if err := os.WriteFile(path, out, filePerm); err != nil { - editError = errors.Join(editError, err) + editError = errors.Join(editError, fmt.Errorf("%s: %w", path, err)) continue } } From 936e333de73b34df1fa6bbf78fa9eed082dd326a Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 17 Dec 2024 19:48:10 +0100 Subject: [PATCH 57/57] fix: skip suggested changes for cgo --- pkg/goanalysis/runners.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/goanalysis/runners.go b/pkg/goanalysis/runners.go index d71fccb7876f..3a9a35dec153 100644 --- a/pkg/goanalysis/runners.go +++ b/pkg/goanalysis/runners.go @@ -3,6 +3,7 @@ package goanalysis import ( "fmt" "go/token" + "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" @@ -97,6 +98,13 @@ func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) st var suggestedFixes []analysis.SuggestedFix for _, sf := range diag.SuggestedFixes { + // Skip suggested fixes on cgo files. + // The related error is: "diff has out-of-bounds edits" + // This is a temporary workaround. + if !strings.HasSuffix(diag.File.Name(), ".go") { + continue + } + nsf := analysis.SuggestedFix{Message: sf.Message} for _, edit := range sf.TextEdits {