cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.52.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/reporter/console.go

167lines · modecode

1package reporter
2
3import (
4 "fmt"
5 "io"
6 "log/slog"
7 "os"
8 "sort"
9 "strconv"
10 "strings"
11
12 "github.com/fatih/color"
13 "golang.org/x/exp/slices"
14
15 "github.com/cloudflare/pint/internal/checks"
16)
17
18func NewConsoleReporter(output io.Writer, minSeverity checks.Severity) ConsoleReporter {
19 return ConsoleReporter{output: output, minSeverity: minSeverity}
20}
21
22type ConsoleReporter struct {
23 output io.Writer
24 minSeverity checks.Severity
25}
26
27func (cr ConsoleReporter) Submit(summary Summary) (err error) {
28 reports := summary.Reports()
29 sort.Slice(reports, func(i, j int) bool {
30 if reports[i].SourcePath < reports[j].SourcePath {
31 return true
32 }
33 if reports[i].SourcePath > reports[j].SourcePath {
34 return false
35 }
36 if reports[i].Problem.Lines[0] < reports[j].Problem.Lines[0] {
37 return true
38 }
39 if reports[i].Problem.Lines[0] > reports[j].Problem.Lines[0] {
40 return false
41 }
42 if reports[i].Problem.Reporter < reports[j].Problem.Reporter {
43 return true
44 }
45 if reports[i].Problem.Reporter > reports[j].Problem.Reporter {
46 return false
47 }
48 return reports[i].Problem.Text < reports[j].Problem.Text
49 })
50
51 perFile := map[string][]string{}
52 for _, report := range reports {
53 if report.Problem.Severity < cr.minSeverity {
54 continue
55 }
56
57 if _, ok := perFile[report.SourcePath]; !ok {
58 perFile[report.SourcePath] = []string{}
59 }
60
61 var content string
62 if report.Problem.Anchor == checks.AnchorAfter {
63 content, err = readFile(report.SourcePath)
64 if err != nil {
65 return err
66 }
67 }
68
69 msg := []string{}
70
71 firstLine, lastLine := report.Problem.LineRange()
72
73 path := report.SourcePath
74 if report.SourcePath != report.ReportedPath {
75 path = fmt.Sprintf("%s ~> %s", report.SourcePath, report.ReportedPath)
76 }
77 path = color.CyanString("%s:%s", path, printLineRange(firstLine, lastLine))
78 if report.Problem.Anchor == checks.AnchorBefore {
79 path += " " + color.RedString("(deleted)")
80 }
81 path += " "
82
83 msg = append(msg, path)
84 switch report.Problem.Severity {
85 case checks.Bug, checks.Fatal:
86 msg = append(msg, color.RedString("%s: %s", report.Problem.Severity, report.Problem.Text))
87 case checks.Warning:
88 msg = append(msg, color.YellowString("%s: %s", report.Problem.Severity, report.Problem.Text))
89 case checks.Information:
90 msg = append(msg, color.HiBlackString("%s: %s", report.Problem.Severity, report.Problem.Text))
91 }
92 msg = append(msg, color.MagentaString(" (%s)\n", report.Problem.Reporter))
93
94 if report.Problem.Anchor == checks.AnchorAfter {
95 lines := strings.Split(content, "\n")
96 if lastLine > len(lines)-1 {
97 lastLine = len(lines) - 1
98 slog.Warn(
99 "Tried to read more lines than present in the source file, this is likely due to '\n' usage in some rules, see https://github.com/cloudflare/pint/issues/20 for details",
100 slog.String("path", report.SourcePath),
101 )
102 }
103
104 nrFmt := fmt.Sprintf("%%%dd", countDigits(lastLine)+1)
105 var inPlaceholder bool
106 for i := firstLine; i <= lastLine; i++ {
107 switch {
108 case slices.Contains(report.Problem.Lines, i):
109 msg = append(msg, color.WhiteString(nrFmt+" | %s\n", i, lines[i-1]))
110 inPlaceholder = false
111 case inPlaceholder:
112 //
113 default:
114 msg = append(msg, color.WhiteString(" %s\n", strings.Repeat(".", countDigits(lastLine))))
115 inPlaceholder = true
116 }
117 }
118 }
119
120 perFile[report.SourcePath] = append(perFile[report.SourcePath], strings.Join(msg, ""))
121 }
122
123 paths := []string{}
124 for path := range perFile {
125 paths = append(paths, path)
126 }
127 sort.Strings(paths)
128
129 for _, path := range paths {
130 msgs := perFile[path]
131 for _, msg := range msgs {
132 fmt.Fprintln(cr.output, msg)
133 }
134 }
135
136 return nil
137}
138
139func readFile(path string) (string, error) {
140 f, err := os.Open(path)
141 if err != nil {
142 return "", err
143 }
144 defer f.Close()
145
146 content, err := io.ReadAll(f)
147 if err != nil {
148 return "", err
149 }
150
151 return string(content), nil
152}
153
154func printLineRange(s, e int) string {
155 if s == e {
156 return strconv.Itoa(s)
157 }
158 return fmt.Sprintf("%d-%d", s, e)
159}
160
161func countDigits(n int) (c int) {
162 for n > 0 {
163 n /= 10
164 c++
165 }
166 return c
167}
168