Skip to content
This repository was archived by the owner on Jun 2, 2023. It is now read-only.

Commit b779f54

Browse files
committed
gocodescore: implement the first version
1 parent ba7af94 commit b779f54

File tree

3 files changed

+271
-0
lines changed

3 files changed

+271
-0
lines changed

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ linters:
3434
- scopelint
3535
- dupl
3636
- interfacer
37+
- wsl
38+
- godox
39+
- funlen
40+
- whitespace
3741

3842
# golangci.com configuration
3943
# https://github.com/golangci/golangci/wiki/Configuration

cmd/gocodescore/main.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"os/exec"
8+
9+
"github.com/golangci/golangci-api/internal/api/score"
10+
11+
"github.com/golangci/golangci-lint/pkg/printers"
12+
)
13+
14+
func main() {
15+
cmd := exec.Command("golangci-lint", "run", "--out-format=json", "--issues-exit-code=0")
16+
out, err := cmd.Output()
17+
if err != nil {
18+
log.Fatalf("Failed to run golangci-lint: %s", err)
19+
}
20+
21+
var runRes printers.JSONResult
22+
if err = json.Unmarshal(out, &runRes); err != nil {
23+
log.Fatalf("Failed to json unmarshal golangci-lint output %s: %s", string(out), err)
24+
}
25+
26+
calcRes := score.Calculator{}.Calc(&runRes)
27+
fmt.Printf("Score: %d/%d\n", calcRes.Score, calcRes.MaxScore)
28+
if len(calcRes.Recommendations) != 0 {
29+
for _, rec := range calcRes.Recommendations {
30+
fmt.Printf(" - get %d more score: %s\n", rec.ScoreIncrease, rec.Text)
31+
}
32+
}
33+
}

internal/api/score/calculator.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package score
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"sort"
7+
"strings"
8+
9+
"github.com/golangci/golangci-lint/pkg/printers"
10+
)
11+
12+
type Calculator struct{}
13+
14+
type Recommendation struct {
15+
Text string
16+
ScoreIncrease int // [0; 100], how much score can be gained if perform recommendation
17+
}
18+
19+
type CalcResult struct {
20+
Score int // [0; 100]
21+
MaxScore int
22+
Recommendations []Recommendation
23+
}
24+
25+
type weightedLinter struct {
26+
name string // linter name
27+
weight float64 // importance of linter
28+
}
29+
30+
func (c Calculator) Calc(runRes *printers.JSONResult) *CalcResult {
31+
const maxScore = 100
32+
33+
if runRes.Report == nil {
34+
return &CalcResult{
35+
Score: maxScore,
36+
MaxScore: maxScore,
37+
}
38+
}
39+
40+
var recomendations []Recommendation
41+
if rec := c.buildRecommendationForDisabledLinters(runRes); rec != nil {
42+
recomendations = append(recomendations, *rec)
43+
}
44+
45+
if rec := c.buildRecommendationForIssues(runRes); rec != nil {
46+
recomendations = append(recomendations, *rec)
47+
}
48+
49+
score := maxScore
50+
for _, rec := range recomendations {
51+
score -= rec.ScoreIncrease
52+
}
53+
54+
return &CalcResult{
55+
Score: score,
56+
MaxScore: maxScore,
57+
Recommendations: recomendations,
58+
}
59+
}
60+
61+
func (c Calculator) buildRecommendationForDisabledLinters(runRes *printers.JSONResult) *Recommendation {
62+
enabledLinters := map[string]bool{}
63+
for _, linter := range runRes.Report.Linters {
64+
if linter.Enabled {
65+
enabledLinters[linter.Name] = true
66+
}
67+
}
68+
69+
linters := c.getNeededLinters(enabledLinters)
70+
71+
var disabledNeededLinters []weightedLinter
72+
for _, wl := range linters {
73+
if !enabledLinters[wl.name] {
74+
disabledNeededLinters = append(disabledNeededLinters, wl)
75+
}
76+
}
77+
78+
if len(disabledNeededLinters) == 0 {
79+
return nil
80+
}
81+
82+
weight := float64(0)
83+
var disabledNeededLinterNames []string
84+
for _, wl := range disabledNeededLinters {
85+
weight += wl.weight
86+
disabledNeededLinterNames = append(disabledNeededLinterNames, wl.name)
87+
}
88+
89+
sort.Strings(disabledNeededLinterNames)
90+
91+
const maxScore = 100
92+
score := int(weight * maxScore)
93+
if score == 0 { // rounded to zero
94+
return nil
95+
}
96+
97+
return &Recommendation{
98+
ScoreIncrease: score,
99+
Text: fmt.Sprintf("enable linters %s", strings.Join(disabledNeededLinterNames, ", ")),
100+
}
101+
}
102+
103+
//nolint:gocyclo
104+
func (c Calculator) buildRecommendationForIssues(runRes *printers.JSONResult) *Recommendation {
105+
enabledLinters := map[string]bool{}
106+
for _, linter := range runRes.Report.Linters {
107+
if linter.Enabled {
108+
enabledLinters[linter.Name] = true
109+
}
110+
}
111+
112+
linters := c.getNeededLinters(enabledLinters)
113+
114+
lintersMap := map[string]*weightedLinter{}
115+
for i := range linters {
116+
lintersMap[linters[i].name] = &linters[i]
117+
}
118+
119+
issuesPerLinter := map[string]int{}
120+
for _, issue := range runRes.Issues {
121+
issuesPerLinter[issue.FromLinter]++
122+
}
123+
124+
if len(issuesPerLinter) == 0 {
125+
return nil
126+
}
127+
128+
weight := float64(0)
129+
for linter, issueCount := range issuesPerLinter {
130+
wl := lintersMap[linter]
131+
if wl == nil {
132+
continue // not needed linter
133+
}
134+
135+
if issueCount > 100 {
136+
issueCount = 100
137+
}
138+
139+
// 100 -> 1, 50 -> 0.85, 10 -> 0.5, 5 -> 0.35, 1 -> 0
140+
normalizedLog := math.Log10(float64(issueCount)) / 2
141+
const minScoreForAnyIssue = 0.2
142+
weight += wl.weight * (minScoreForAnyIssue + (1-minScoreForAnyIssue)*normalizedLog)
143+
}
144+
145+
var neededLintersWithIssues []string
146+
for linter := range issuesPerLinter {
147+
if _, ok := lintersMap[linter]; ok {
148+
neededLintersWithIssues = append(neededLintersWithIssues, linter)
149+
}
150+
}
151+
152+
sort.Strings(neededLintersWithIssues)
153+
154+
const maxScore = 100
155+
score := int(weight * maxScore)
156+
if score == 0 { // rounded to zero
157+
return nil
158+
}
159+
160+
return &Recommendation{
161+
ScoreIncrease: score,
162+
Text: fmt.Sprintf("fix issues from linters %s", strings.Join(neededLintersWithIssues, ", ")),
163+
}
164+
}
165+
166+
func (c Calculator) getNeededLinters(enabledLinters map[string]bool) []weightedLinter {
167+
bugsLinters := c.getNeededBugsLintersWeights()
168+
styleLinters := c.getNeededStyleLintersWeights(enabledLinters)
169+
170+
const bugsWeight = 0.7
171+
var linters []weightedLinter
172+
for _, wl := range bugsLinters {
173+
wl.weight *= bugsWeight
174+
linters = append(linters, wl)
175+
}
176+
for _, wl := range styleLinters {
177+
wl.weight *= 1 - bugsWeight
178+
linters = append(linters, wl)
179+
}
180+
181+
return linters
182+
}
183+
184+
func (c Calculator) normalizeWeightedLinters(linters []weightedLinter) []weightedLinter {
185+
res := make([]weightedLinter, 0, len(linters))
186+
totalWeight := float64(0)
187+
for _, wl := range linters {
188+
totalWeight += wl.weight
189+
}
190+
191+
for _, wl := range linters {
192+
res = append(res, weightedLinter{wl.name, wl.weight / totalWeight})
193+
}
194+
195+
return res
196+
}
197+
198+
func (c Calculator) getNeededBugsLintersWeights() []weightedLinter {
199+
return c.normalizeWeightedLinters([]weightedLinter{
200+
{"govet", 1},
201+
{"staticcheck", 1},
202+
{"errcheck", 0.8},
203+
{"bodyclose", 0.7}, // low because can have false-positives
204+
{"typecheck", 0.5},
205+
})
206+
}
207+
208+
func (c Calculator) getNeededStyleLintersWeights(enabledLinters map[string]bool) []weightedLinter {
209+
linters := []weightedLinter{
210+
{"goimports", 1},
211+
{"dogsled", 0.5},
212+
{"gochecknoglobals", 0.4}, // low because can have false-positives
213+
{"gochecknoinits", 0.4},
214+
{"goconst", 0.3},
215+
{"golint", 1},
216+
{"gosimple", 0.6},
217+
{"lll", 0.1},
218+
{"misspell", 0.4},
219+
{"unconvert", 0.4},
220+
{"ineffassign", 0.5},
221+
}
222+
223+
const (
224+
gocognit = "gocognit"
225+
gocyclo = "gocyclo"
226+
)
227+
complexityLinter := gocognit
228+
if !enabledLinters[gocognit] && enabledLinters[gocyclo] {
229+
complexityLinter = gocyclo
230+
}
231+
linters = append(linters, weightedLinter{complexityLinter, 0.8})
232+
233+
return c.normalizeWeightedLinters(linters)
234+
}

0 commit comments

Comments
 (0)