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 }