github.com/shulhan/golangci-lint@v1.10.1/pkg/golinters/gofmt.go (about)

     1  package golinters
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"go/token"
     8  
     9  	gofmtAPI "github.com/golangci/gofmt/gofmt"
    10  	goimportsAPI "github.com/golangci/gofmt/goimports"
    11  	"github.com/golangci/golangci-lint/pkg/lint/linter"
    12  	"github.com/golangci/golangci-lint/pkg/logutils"
    13  	"github.com/golangci/golangci-lint/pkg/result"
    14  	"sourcegraph.com/sourcegraph/go-diff/diff"
    15  )
    16  
    17  type Gofmt struct {
    18  	UseGoimports bool
    19  }
    20  
    21  func (g Gofmt) Name() string {
    22  	if g.UseGoimports {
    23  		return "goimports"
    24  	}
    25  
    26  	return "gofmt"
    27  }
    28  
    29  func (g Gofmt) Desc() string {
    30  	if g.UseGoimports {
    31  		return "Goimports does everything that gofmt does. Additionally it checks unused imports"
    32  	}
    33  
    34  	return "Gofmt checks whether code was gofmt-ed. By default " +
    35  		"this tool runs with -s option to check for code simplification"
    36  }
    37  
    38  func getFirstDeletedAndAddedLineNumberInHunk(h *diff.Hunk) (int, int, error) {
    39  	lines := bytes.Split(h.Body, []byte{'\n'})
    40  	lineNumber := int(h.OrigStartLine - 1)
    41  	firstAddedLineNumber := -1
    42  	for _, line := range lines {
    43  		lineNumber++
    44  
    45  		if len(line) == 0 {
    46  			continue
    47  		}
    48  		if line[0] == '+' && firstAddedLineNumber == -1 {
    49  			firstAddedLineNumber = lineNumber
    50  		}
    51  		if line[0] == '-' {
    52  			return lineNumber, firstAddedLineNumber, nil
    53  		}
    54  	}
    55  
    56  	return 0, firstAddedLineNumber, fmt.Errorf("didn't find deletion line in hunk %s", string(h.Body))
    57  }
    58  
    59  func (g Gofmt) extractIssuesFromPatch(patch string, log logutils.Log) ([]result.Issue, error) {
    60  	diffs, err := diff.ParseMultiFileDiff([]byte(patch))
    61  	if err != nil {
    62  		return nil, fmt.Errorf("can't parse patch: %s", err)
    63  	}
    64  
    65  	if len(diffs) == 0 {
    66  		return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs)
    67  	}
    68  
    69  	issues := []result.Issue{}
    70  	for _, d := range diffs {
    71  		if len(d.Hunks) == 0 {
    72  			log.Warnf("Got no hunks in diff %+v", d)
    73  			continue
    74  		}
    75  
    76  		for _, hunk := range d.Hunks {
    77  			deletedLine, addedLine, err := getFirstDeletedAndAddedLineNumberInHunk(hunk)
    78  			if err != nil {
    79  				if addedLine > 1 {
    80  					deletedLine = addedLine - 1 // use previous line, TODO: use both prev and next lines
    81  				} else {
    82  					deletedLine = 1
    83  				}
    84  			}
    85  
    86  			text := "File is not `gofmt`-ed with `-s`"
    87  			if g.UseGoimports {
    88  				text = "File is not `goimports`-ed"
    89  			}
    90  			i := result.Issue{
    91  				FromLinter: g.Name(),
    92  				Pos: token.Position{
    93  					Filename: d.NewName,
    94  					Line:     deletedLine,
    95  				},
    96  				Text: text,
    97  			}
    98  			issues = append(issues, i)
    99  		}
   100  	}
   101  
   102  	return issues, nil
   103  }
   104  
   105  func (g Gofmt) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
   106  	var issues []result.Issue
   107  
   108  	for _, f := range lintCtx.PkgProgram.Files(lintCtx.Cfg.Run.AnalyzeTests) {
   109  		var diff []byte
   110  		var err error
   111  		if g.UseGoimports {
   112  			diff, err = goimportsAPI.Run(f)
   113  		} else {
   114  			diff, err = gofmtAPI.Run(f, lintCtx.Settings().Gofmt.Simplify)
   115  		}
   116  		if err != nil { // TODO: skip
   117  			return nil, err
   118  		}
   119  		if diff == nil {
   120  			continue
   121  		}
   122  
   123  		is, err := g.extractIssuesFromPatch(string(diff), lintCtx.Log)
   124  		if err != nil {
   125  			return nil, fmt.Errorf("can't extract issues from gofmt diff output %q: %s", string(diff), err)
   126  		}
   127  
   128  		issues = append(issues, is...)
   129  	}
   130  
   131  	return issues, nil
   132  }