cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.49.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/reporter/console.go

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