github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/golinters/revive.go (about)

     1  package golinters
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"go/token"
     8  	"io/ioutil"
     9  	"reflect"
    10  
    11  	"github.com/BurntSushi/toml"
    12  	"github.com/mgechev/dots"
    13  	reviveConfig "github.com/mgechev/revive/config"
    14  	"github.com/mgechev/revive/lint"
    15  	"github.com/mgechev/revive/rule"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/tools/go/analysis"
    18  
    19  	"github.com/elek/golangci-lint/pkg/config"
    20  	"github.com/elek/golangci-lint/pkg/golinters/goanalysis"
    21  	"github.com/elek/golangci-lint/pkg/lint/linter"
    22  	"github.com/elek/golangci-lint/pkg/logutils"
    23  	"github.com/elek/golangci-lint/pkg/result"
    24  )
    25  
    26  const reviveName = "revive"
    27  
    28  var reviveDebugf = logutils.Debug("revive")
    29  
    30  // jsonObject defines a JSON object of a failure
    31  type jsonObject struct {
    32  	Severity     lint.Severity
    33  	lint.Failure `json:",inline"`
    34  }
    35  
    36  // NewRevive returns a new Revive linter.
    37  func NewRevive(cfg *config.ReviveSettings) *goanalysis.Linter {
    38  	var issues []goanalysis.Issue
    39  
    40  	analyzer := &analysis.Analyzer{
    41  		Name: goanalysis.TheOnlyAnalyzerName,
    42  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    43  	}
    44  
    45  	return goanalysis.NewLinter(
    46  		reviveName,
    47  		"Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.",
    48  		[]*analysis.Analyzer{analyzer},
    49  		nil,
    50  	).WithContextSetter(func(lintCtx *linter.Context) {
    51  		analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
    52  			var files []string
    53  
    54  			for _, file := range pass.Files {
    55  				files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename)
    56  			}
    57  
    58  			conf, err := getReviveConfig(cfg)
    59  			if err != nil {
    60  				return nil, err
    61  			}
    62  
    63  			formatter, err := reviveConfig.GetFormatter("json")
    64  			if err != nil {
    65  				return nil, err
    66  			}
    67  
    68  			revive := lint.New(ioutil.ReadFile)
    69  
    70  			lintingRules, err := reviveConfig.GetLintingRules(conf)
    71  			if err != nil {
    72  				return nil, err
    73  			}
    74  
    75  			packages, err := dots.ResolvePackages(files, []string{})
    76  			if err != nil {
    77  				return nil, err
    78  			}
    79  
    80  			failures, err := revive.Lint(packages, lintingRules, *conf)
    81  			if err != nil {
    82  				return nil, err
    83  			}
    84  
    85  			formatChan := make(chan lint.Failure)
    86  			exitChan := make(chan bool)
    87  
    88  			var output string
    89  			go func() {
    90  				output, err = formatter.Format(formatChan, *conf)
    91  				if err != nil {
    92  					lintCtx.Log.Errorf("Format error: %v", err)
    93  				}
    94  				exitChan <- true
    95  			}()
    96  
    97  			for f := range failures {
    98  				if f.Confidence < conf.Confidence {
    99  					continue
   100  				}
   101  
   102  				formatChan <- f
   103  			}
   104  
   105  			close(formatChan)
   106  			<-exitChan
   107  
   108  			var results []jsonObject
   109  			err = json.Unmarshal([]byte(output), &results)
   110  			if err != nil {
   111  				return nil, err
   112  			}
   113  
   114  			for i := range results {
   115  				issues = append(issues, reviveToIssue(pass, &results[i]))
   116  			}
   117  
   118  			return nil, nil
   119  		}
   120  	}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
   121  		return issues
   122  	}).WithLoadMode(goanalysis.LoadModeSyntax)
   123  }
   124  
   125  func reviveToIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue {
   126  	lineRangeTo := object.Position.End.Line
   127  	if object.RuleName == (&rule.ExportedRule{}).Name() {
   128  		lineRangeTo = object.Position.Start.Line
   129  	}
   130  
   131  	return goanalysis.NewIssue(&result.Issue{
   132  		Severity: string(object.Severity),
   133  		Text:     fmt.Sprintf("%s: %s", object.RuleName, object.Failure.Failure),
   134  		Pos: token.Position{
   135  			Filename: object.Position.Start.Filename,
   136  			Line:     object.Position.Start.Line,
   137  			Offset:   object.Position.Start.Offset,
   138  			Column:   object.Position.Start.Column,
   139  		},
   140  		LineRange: &result.Range{
   141  			From: object.Position.Start.Line,
   142  			To:   lineRangeTo,
   143  		},
   144  		FromLinter: reviveName,
   145  	}, pass)
   146  }
   147  
   148  // This function mimics the GetConfig function of revive.
   149  // This allow to get default values and right types.
   150  // https://github.com/golangci/golangci-lint/issues/1745
   151  // https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L155
   152  func getReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) {
   153  	conf := defaultConfig()
   154  
   155  	if !reflect.DeepEqual(cfg, &config.ReviveSettings{}) {
   156  		rawRoot := createConfigMap(cfg)
   157  		buf := bytes.NewBuffer(nil)
   158  
   159  		err := toml.NewEncoder(buf).Encode(rawRoot)
   160  		if err != nil {
   161  			return nil, errors.Wrap(err, "failed to encode configuration")
   162  		}
   163  
   164  		conf = &lint.Config{}
   165  		_, err = toml.DecodeReader(buf, conf)
   166  		if err != nil {
   167  			return nil, errors.Wrap(err, "failed to decode configuration")
   168  		}
   169  	}
   170  
   171  	normalizeConfig(conf)
   172  
   173  	reviveDebugf("revive configuration: %#v", conf)
   174  
   175  	return conf, nil
   176  }
   177  
   178  func createConfigMap(cfg *config.ReviveSettings) map[string]interface{} {
   179  	rawRoot := map[string]interface{}{
   180  		"ignoreGeneratedHeader": cfg.IgnoreGeneratedHeader,
   181  		"confidence":            cfg.Confidence,
   182  		"severity":              cfg.Severity,
   183  		"errorCode":             cfg.ErrorCode,
   184  		"warningCode":           cfg.WarningCode,
   185  		"enableAllRules":        cfg.EnableAllRules,
   186  	}
   187  
   188  	rawDirectives := map[string]map[string]interface{}{}
   189  	for _, directive := range cfg.Directives {
   190  		rawDirectives[directive.Name] = map[string]interface{}{
   191  			"severity": directive.Severity,
   192  		}
   193  	}
   194  
   195  	if len(rawDirectives) > 0 {
   196  		rawRoot["directive"] = rawDirectives
   197  	}
   198  
   199  	rawRules := map[string]map[string]interface{}{}
   200  	for _, s := range cfg.Rules {
   201  		rawRules[s.Name] = map[string]interface{}{
   202  			"severity":  s.Severity,
   203  			"arguments": safeTomlSlice(s.Arguments),
   204  			"disabled":  s.Disabled,
   205  		}
   206  	}
   207  
   208  	if len(rawRules) > 0 {
   209  		rawRoot["rule"] = rawRules
   210  	}
   211  
   212  	return rawRoot
   213  }
   214  
   215  func safeTomlSlice(r []interface{}) []interface{} {
   216  	if len(r) == 0 {
   217  		return nil
   218  	}
   219  
   220  	if _, ok := r[0].(map[interface{}]interface{}); !ok {
   221  		return r
   222  	}
   223  
   224  	var typed []interface{}
   225  	for _, elt := range r {
   226  		item := map[string]interface{}{}
   227  		for k, v := range elt.(map[interface{}]interface{}) {
   228  			item[k.(string)] = v
   229  		}
   230  
   231  		typed = append(typed, item)
   232  	}
   233  
   234  	return typed
   235  }
   236  
   237  // This element is not exported by revive, so we need copy the code.
   238  // Extracted from https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L15
   239  var defaultRules = []lint.Rule{
   240  	&rule.VarDeclarationsRule{},
   241  	&rule.PackageCommentsRule{},
   242  	&rule.DotImportsRule{},
   243  	&rule.BlankImportsRule{},
   244  	&rule.ExportedRule{},
   245  	&rule.VarNamingRule{},
   246  	&rule.IndentErrorFlowRule{},
   247  	&rule.RangeRule{},
   248  	&rule.ErrorfRule{},
   249  	&rule.ErrorNamingRule{},
   250  	&rule.ErrorStringsRule{},
   251  	&rule.ReceiverNamingRule{},
   252  	&rule.IncrementDecrementRule{},
   253  	&rule.ErrorReturnRule{},
   254  	&rule.UnexportedReturnRule{},
   255  	&rule.TimeNamingRule{},
   256  	&rule.ContextKeysType{},
   257  	&rule.ContextAsArgumentRule{},
   258  }
   259  
   260  // This element is not exported by revive, so we need copy the code.
   261  // Extracted from https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L133
   262  func normalizeConfig(cfg *lint.Config) {
   263  	if cfg.Confidence == 0 {
   264  		cfg.Confidence = 0.8
   265  	}
   266  	severity := cfg.Severity
   267  	if severity != "" {
   268  		for k, v := range cfg.Rules {
   269  			if v.Severity == "" {
   270  				v.Severity = severity
   271  			}
   272  			cfg.Rules[k] = v
   273  		}
   274  		for k, v := range cfg.Directives {
   275  			if v.Severity == "" {
   276  				v.Severity = severity
   277  			}
   278  			cfg.Directives[k] = v
   279  		}
   280  	}
   281  }
   282  
   283  // This element is not exported by revive, so we need copy the code.
   284  // Extracted from https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L182
   285  func defaultConfig() *lint.Config {
   286  	defaultConfig := lint.Config{
   287  		Confidence: 0.0,
   288  		Severity:   lint.SeverityWarning,
   289  		Rules:      map[string]lint.RuleConfig{},
   290  	}
   291  	for _, r := range defaultRules {
   292  		defaultConfig.Rules[r.Name()] = lint.RuleConfig{}
   293  	}
   294  	return &defaultConfig
   295  }