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 }