cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/diags/problems.go

155lines · modecode

1package diags
2
3import (
4 "cmp"
5 "fmt"
6 "slices"
7 "strings"
8
9 "github.com/cloudflare/pint/internal/output"
10)
11
12func countDigits(n int) (c int) {
13 for n > 0 {
14 n /= 10
15 c++
16 }
17 return c
18}
19
20type Kind uint8
21
22const (
23 Issue Kind = iota
24 Context
25)
26
27type Diagnostic struct {
28 Message string
29 Pos PositionRanges `yaml:"-"`
30 FirstColumn int // 1-indexed
31 LastColumn int // 1-indexed
32 Kind Kind
33}
34
35func lineCoverage(diags []Diagnostic) (lines []int) {
36 for _, diag := range diags {
37 for _, pos := range diag.Pos {
38 if !slices.Contains(lines, pos.Line) {
39 lines = append(lines, pos.Line)
40 }
41 }
42 }
43 slices.Sort(lines)
44 return lines
45}
46
47func InjectDiagnostics(content string, d []Diagnostic, color output.Color) string {
48 diags := make([]Diagnostic, len(d))
49 copy(diags, d)
50
51 lines := lineCoverage(diags)
52 lastLine := slices.Max(lines)
53
54 slices.SortStableFunc(diags, func(a, b Diagnostic) int {
55 return cmp.Compare(b.FirstColumn, a.FirstColumn)
56 })
57
58 diagPositions := make([]PositionRanges, len(diags))
59 for i, diag := range diags {
60 dl := diag.Pos.Len()
61 diagPositions[i] = readRange(
62 min(diag.FirstColumn, dl),
63 min(diag.LastColumn, dl),
64 diag.Pos,
65 )
66 }
67
68 var buf strings.Builder
69 nextLine := make([]strings.Builder, len(diags))
70 needsNextLine := make([]bool, len(diags))
71
72 disablePoints := make([]bool, len(diags))
73 for i, a := range diags {
74 for j := range i {
75 b := diags[j]
76 if a.FirstColumn == b.FirstColumn && a.LastColumn == b.LastColumn {
77 disablePoints[i] = true
78 }
79 }
80 }
81
82 digits := countDigits(lastLine)
83 nrFmt := fmt.Sprintf("%%%dd", digits)
84
85 var lastWriteLine int
86 for lineIndex, line := range strings.Split(content, "\n") {
87
88 if lineIndex+1 > lastLine {
89 break
90 }
91 if !slices.Contains(lines, lineIndex+1) {
92 continue
93 }
94
95 for i := range diags {
96 needsNextLine[i] = false
97 if lineIndex+1 == diagPositions[i].Lines().Last {
98 needsNextLine[i] = true
99 }
100 }
101
102 prefix := fmt.Sprintf(nrFmt+" | ", lineIndex+1)
103
104 if lastWriteLine > 0 && lineIndex+1-lastWriteLine > 1 {
105 buf.WriteString(output.MaybeColor(output.White, color == output.None, strings.Repeat(" ", digits)))
106 buf.WriteString(" | [...]\n")
107 }
108 lastWriteLine = lineIndex + 1
109
110 buf.WriteString(output.MaybeColor(output.White, color == output.None, prefix))
111 for i, ok := range needsNextLine {
112 if ok {
113 nextLine[i].WriteString(strings.Repeat(" ", digits+3))
114 }
115 }
116
117 for columnIndex, r := range line {
118 buf.WriteRune(r)
119
120 for i, ok := range needsNextLine {
121 if !ok {
122 continue
123 }
124 for _, pos := range diagPositions[i] {
125 if pos.Line != lineIndex+1 {
126 continue
127 }
128 before := pos.FirstColumn > columnIndex+1
129 inside := pos.FirstColumn <= columnIndex+1 && pos.LastColumn >= columnIndex+1
130 switch {
131 case before:
132 nextLine[i].WriteRune(' ')
133 case inside && disablePoints[i]:
134 nextLine[i].WriteRune(' ')
135 case inside && !disablePoints[i]:
136 nextLine[i].WriteRune('^')
137 }
138 }
139 }
140 }
141 buf.WriteRune('\n')
142
143 for i, ok := range needsNextLine {
144 if ok {
145 buf.WriteString(output.MaybeColor(color, color == output.None, nextLine[i].String()))
146 buf.WriteRune(' ')
147 buf.WriteString(output.MaybeColor(color, color == output.None, diags[i].Message))
148 buf.WriteRune('\n')
149 nextLine[i].Reset()
150 }
151 }
152 }
153
154 return buf.String()
155}
156