github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/revive.go (about)

     1  package golinters
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"go/token"
     8  	"os"
     9  	"reflect"
    10  	"sync"
    11  
    12  	"github.com/BurntSushi/toml"
    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/golangci/golangci-lint/pkg/config"
    20  	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
    21  	"github.com/golangci/golangci-lint/pkg/lint/linter"
    22  	"github.com/golangci/golangci-lint/pkg/logutils"
    23  	"github.com/golangci/golangci-lint/pkg/result"
    24  )
    25  
    26  const reviveName = "revive"
    27  
    28  var reviveDebugf = logutils.Debug(logutils.DebugKeyRevive)
    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  //
    38  //nolint:dupl
    39  func NewRevive(settings *config.ReviveSettings) *goanalysis.Linter {
    40  	var mu sync.Mutex
    41  	var resIssues []goanalysis.Issue
    42  
    43  	analyzer := &analysis.Analyzer{
    44  		Name: goanalysis.TheOnlyAnalyzerName,
    45  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    46  		Run:  goanalysis.DummyRun,
    47  	}
    48  
    49  	return goanalysis.NewLinter(
    50  		reviveName,
    51  		"Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.",
    52  		[]*analysis.Analyzer{analyzer},
    53  		nil,
    54  	).WithContextSetter(func(lintCtx *linter.Context) {
    55  		analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
    56  			issues, err := runRevive(lintCtx, pass, settings)
    57  			if err != nil {
    58  				return nil, err
    59  			}
    60  
    61  			if len(issues) == 0 {
    62  				return nil, nil
    63  			}
    64  
    65  			mu.Lock()
    66  			resIssues = append(resIssues, issues...)
    67  			mu.Unlock()
    68  
    69  			return nil, nil
    70  		}
    71  	}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
    72  		return resIssues
    73  	}).WithLoadMode(goanalysis.LoadModeSyntax)
    74  }
    75  
    76  func runRevive(lintCtx *linter.Context, pass *analysis.Pass, settings *config.ReviveSettings) ([]goanalysis.Issue, error) {
    77  	packages := [][]string{getFileNames(pass)}
    78  
    79  	conf, err := getReviveConfig(settings)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	formatter, err := reviveConfig.GetFormatter("json")
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	revive := lint.New(os.ReadFile, settings.MaxOpenFiles)
    90  
    91  	lintingRules, err := reviveConfig.GetLintingRules(conf, []lint.Rule{})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	failures, err := revive.Lint(packages, lintingRules, *conf)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	formatChan := make(chan lint.Failure)
   102  	exitChan := make(chan bool)
   103  
   104  	var output string
   105  	go func() {
   106  		output, err = formatter.Format(formatChan, *conf)
   107  		if err != nil {
   108  			lintCtx.Log.Errorf("Format error: %v", err)
   109  		}
   110  		exitChan <- true
   111  	}()
   112  
   113  	for f := range failures {
   114  		if f.Confidence < conf.Confidence {
   115  			continue
   116  		}
   117  
   118  		formatChan <- f
   119  	}
   120  
   121  	close(formatChan)
   122  	<-exitChan
   123  
   124  	var results []jsonObject
   125  	err = json.Unmarshal([]byte(output), &results)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	var issues []goanalysis.Issue
   131  	for i := range results {
   132  		issues = append(issues, reviveToIssue(pass, &results[i]))
   133  	}
   134  
   135  	return issues, nil
   136  }
   137  
   138  func reviveToIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue {
   139  	lineRangeTo := object.Position.End.Line
   140  	if object.RuleName == (&rule.ExportedRule{}).Name() {
   141  		lineRangeTo = object.Position.Start.Line
   142  	}
   143  
   144  	return goanalysis.NewIssue(&result.Issue{
   145  		Severity: string(object.Severity),
   146  		Text:     fmt.Sprintf("%s: %s", object.RuleName, object.Failure.Failure),
   147  		Pos: token.Position{
   148  			Filename: object.Position.Start.Filename,
   149  			Line:     object.Position.Start.Line,
   150  			Offset:   object.Position.Start.Offset,
   151  			Column:   object.Position.Start.Column,
   152  		},
   153  		LineRange: &result.Range{
   154  			From: object.Position.Start.Line,
   155  			To:   lineRangeTo,
   156  		},
   157  		FromLinter: reviveName,
   158  	}, pass)
   159  }
   160  
   161  // This function mimics the GetConfig function of revive.
   162  // This allows to get default values and right types.
   163  // https://github.com/golangci/golangci-lint/issues/1745
   164  // https://github.com/mgechev/revive/blob/v1.1.4/config/config.go#L182
   165  func getReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) {
   166  	conf := defaultConfig()
   167  
   168  	if !reflect.DeepEqual(cfg, &config.ReviveSettings{}) {
   169  		rawRoot := createConfigMap(cfg)
   170  		buf := bytes.NewBuffer(nil)
   171  
   172  		err := toml.NewEncoder(buf).Encode(rawRoot)
   173  		if err != nil {
   174  			return nil, errors.Wrap(err, "failed to encode configuration")
   175  		}
   176  
   177  		conf = &lint.Config{}
   178  		_, err = toml.NewDecoder(buf).Decode(conf)
   179  		if err != nil {
   180  			return nil, errors.Wrap(err, "failed to decode configuration")
   181  		}
   182  	}
   183  
   184  	normalizeConfig(conf)
   185  
   186  	reviveDebugf("revive configuration: %#v", conf)
   187  
   188  	return conf, nil
   189  }
   190  
   191  func createConfigMap(cfg *config.ReviveSettings) map[string]interface{} {
   192  	rawRoot := map[string]interface{}{
   193  		"ignoreGeneratedHeader": cfg.IgnoreGeneratedHeader,
   194  		"confidence":            cfg.Confidence,
   195  		"severity":              cfg.Severity,
   196  		"errorCode":             cfg.ErrorCode,
   197  		"warningCode":           cfg.WarningCode,
   198  		"enableAllRules":        cfg.EnableAllRules,
   199  	}
   200  
   201  	rawDirectives := map[string]map[string]interface{}{}
   202  	for _, directive := range cfg.Directives {
   203  		rawDirectives[directive.Name] = map[string]interface{}{
   204  			"severity": directive.Severity,
   205  		}
   206  	}
   207  
   208  	if len(rawDirectives) > 0 {
   209  		rawRoot["directive"] = rawDirectives
   210  	}
   211  
   212  	rawRules := map[string]map[string]interface{}{}
   213  	for _, s := range cfg.Rules {
   214  		rawRules[s.Name] = map[string]interface{}{
   215  			"severity":  s.Severity,
   216  			"arguments": safeTomlSlice(s.Arguments),
   217  			"disabled":  s.Disabled,
   218  		}
   219  	}
   220  
   221  	if len(rawRules) > 0 {
   222  		rawRoot["rule"] = rawRules
   223  	}
   224  
   225  	return rawRoot
   226  }
   227  
   228  func safeTomlSlice(r []interface{}) []interface{} {
   229  	if len(r) == 0 {
   230  		return nil
   231  	}
   232  
   233  	if _, ok := r[0].(map[interface{}]interface{}); !ok {
   234  		return r
   235  	}
   236  
   237  	var typed []interface{}
   238  	for _, elt := range r {
   239  		item := map[string]interface{}{}
   240  		for k, v := range elt.(map[interface{}]interface{}) {
   241  			item[k.(string)] = v
   242  		}
   243  
   244  		typed = append(typed, item)
   245  	}
   246  
   247  	return typed
   248  }
   249  
   250  // This element is not exported by revive, so we need copy the code.
   251  // Extracted from https://github.com/mgechev/revive/blob/v1.1.4/config/config.go#L15
   252  var defaultRules = []lint.Rule{
   253  	&rule.VarDeclarationsRule{},
   254  	&rule.PackageCommentsRule{},
   255  	&rule.DotImportsRule{},
   256  	&rule.BlankImportsRule{},
   257  	&rule.ExportedRule{},
   258  	&rule.VarNamingRule{},
   259  	&rule.IndentErrorFlowRule{},
   260  	&rule.RangeRule{},
   261  	&rule.ErrorfRule{},
   262  	&rule.ErrorNamingRule{},
   263  	&rule.ErrorStringsRule{},
   264  	&rule.ReceiverNamingRule{},
   265  	&rule.IncrementDecrementRule{},
   266  	&rule.ErrorReturnRule{},
   267  	&rule.UnexportedReturnRule{},
   268  	&rule.TimeNamingRule{},
   269  	&rule.ContextKeysType{},
   270  	&rule.ContextAsArgumentRule{},
   271  }
   272  
   273  var allRules = append([]lint.Rule{
   274  	&rule.ArgumentsLimitRule{},
   275  	&rule.CyclomaticRule{},
   276  	&rule.FileHeaderRule{},
   277  	&rule.EmptyBlockRule{},
   278  	&rule.SuperfluousElseRule{},
   279  	&rule.ConfusingNamingRule{},
   280  	&rule.GetReturnRule{},
   281  	&rule.ModifiesParamRule{},
   282  	&rule.ConfusingResultsRule{},
   283  	&rule.DeepExitRule{},
   284  	&rule.UnusedParamRule{},
   285  	&rule.UnreachableCodeRule{},
   286  	&rule.AddConstantRule{},
   287  	&rule.FlagParamRule{},
   288  	&rule.UnnecessaryStmtRule{},
   289  	&rule.StructTagRule{},
   290  	&rule.ModifiesValRecRule{},
   291  	&rule.ConstantLogicalExprRule{},
   292  	&rule.BoolLiteralRule{},
   293  	&rule.RedefinesBuiltinIDRule{},
   294  	&rule.ImportsBlacklistRule{},
   295  	&rule.FunctionResultsLimitRule{},
   296  	&rule.MaxPublicStructsRule{},
   297  	&rule.RangeValInClosureRule{},
   298  	&rule.RangeValAddress{},
   299  	&rule.WaitGroupByValueRule{},
   300  	&rule.AtomicRule{},
   301  	&rule.EmptyLinesRule{},
   302  	&rule.LineLengthLimitRule{},
   303  	&rule.CallToGCRule{},
   304  	&rule.DuplicatedImportsRule{},
   305  	&rule.ImportShadowingRule{},
   306  	&rule.BareReturnRule{},
   307  	&rule.UnusedReceiverRule{},
   308  	&rule.UnhandledErrorRule{},
   309  	&rule.CognitiveComplexityRule{},
   310  	&rule.StringOfIntRule{},
   311  	&rule.StringFormatRule{},
   312  	&rule.EarlyReturnRule{},
   313  	&rule.UnconditionalRecursionRule{},
   314  	&rule.IdenticalBranchesRule{},
   315  	&rule.DeferRule{},
   316  	&rule.UnexportedNamingRule{},
   317  	&rule.FunctionLength{},
   318  	&rule.NestedStructs{},
   319  	&rule.IfReturnRule{},
   320  	&rule.UselessBreak{},
   321  	&rule.TimeEqualRule{},
   322  	&rule.BannedCharsRule{},
   323  	&rule.OptimizeOperandsOrderRule{},
   324  	&rule.DataRaceRule{},
   325  }, defaultRules...)
   326  
   327  const defaultConfidence = 0.8
   328  
   329  // This element is not exported by revive, so we need copy the code.
   330  // Extracted from https://github.com/mgechev/revive/blob/v1.1.4/config/config.go#L145
   331  func normalizeConfig(cfg *lint.Config) {
   332  	// NOTE(ldez): this custom section for golangci-lint should be kept.
   333  	// ---
   334  	if cfg.Confidence == 0 {
   335  		cfg.Confidence = defaultConfidence
   336  	}
   337  	if cfg.Severity == "" {
   338  		cfg.Severity = lint.SeverityWarning
   339  	}
   340  	// ---
   341  
   342  	if len(cfg.Rules) == 0 {
   343  		cfg.Rules = map[string]lint.RuleConfig{}
   344  	}
   345  	if cfg.EnableAllRules {
   346  		// Add to the configuration all rules not yet present in it
   347  		for _, rule := range allRules {
   348  			ruleName := rule.Name()
   349  			_, alreadyInConf := cfg.Rules[ruleName]
   350  			if alreadyInConf {
   351  				continue
   352  			}
   353  			// Add the rule with an empty conf for
   354  			cfg.Rules[ruleName] = lint.RuleConfig{}
   355  		}
   356  	}
   357  
   358  	severity := cfg.Severity
   359  	if severity != "" {
   360  		for k, v := range cfg.Rules {
   361  			if v.Severity == "" {
   362  				v.Severity = severity
   363  			}
   364  			cfg.Rules[k] = v
   365  		}
   366  		for k, v := range cfg.Directives {
   367  			if v.Severity == "" {
   368  				v.Severity = severity
   369  			}
   370  			cfg.Directives[k] = v
   371  		}
   372  	}
   373  }
   374  
   375  // This element is not exported by revive, so we need copy the code.
   376  // Extracted from https://github.com/mgechev/revive/blob/v1.1.4/config/config.go#L214
   377  func defaultConfig() *lint.Config {
   378  	defaultConfig := lint.Config{
   379  		Confidence: defaultConfidence,
   380  		Severity:   lint.SeverityWarning,
   381  		Rules:      map[string]lint.RuleConfig{},
   382  	}
   383  	for _, r := range defaultRules {
   384  		defaultConfig.Rules[r.Name()] = lint.RuleConfig{}
   385  	}
   386  	return &defaultConfig
   387  }