forked from rhysd/actionlint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherror.go
142 lines (123 loc) · 3.38 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package actionlint
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
"github.com/fatih/color"
"github.com/mattn/go-runewidth"
)
var (
bold = color.New(color.Bold)
green = color.New(color.FgGreen)
yellow = color.New(color.FgYellow)
gray = color.New(color.FgHiBlack)
)
// Error represents an error detected by actionlint rules
type Error struct {
// Message is an error message.
Message string
// Filepath is a file path where the error occurred.
Filepath string
// Line is a line number where the error occurred. This value is 1-based.
Line int
// Column is a column number where the error occurred. This value is 1-based.
Column int
// Kind is a string to represent kind of the error. Usually rule name which found the error.
Kind string
}
// Error returns summary of the error as string.
func (e *Error) Error() string {
return fmt.Sprintf("%s:%d:%d: %s [%s]", e.Filepath, e.Line, e.Column, e.Message, e.Kind)
}
func errorAt(pos *Pos, kind string, msg string) *Error {
return &Error{
Message: msg,
Line: pos.Line,
Column: pos.Col,
Kind: kind,
}
}
func errorfAt(pos *Pos, kind string, format string, args ...interface{}) *Error {
return &Error{
Message: fmt.Sprintf(format, args...),
Line: pos.Line,
Column: pos.Col,
Kind: kind,
}
}
// PrettyPrint prints the error with user-friendly way. It prints file name, source position, error
// message with colorful output and source snippet with indicator. When nil is set to source, no
// source snippet is not printed. To disable colorful output, set true to fatih/color.NoColor.
func (e *Error) PrettyPrint(w io.Writer, source []byte) {
yellow.Fprint(w, e.Filepath)
gray.Fprint(w, ":")
fmt.Fprint(w, e.Line)
gray.Fprint(w, ":")
fmt.Fprint(w, e.Column)
gray.Fprint(w, ": ")
bold.Fprint(w, e.Message)
gray.Fprintf(w, " [%s]\n", e.Kind)
if len(source) == 0 || e.Line <= 0 {
return
}
line, ok := e.getLine(source)
if !ok || len(line) < e.Column-1 {
return
}
lnum := fmt.Sprintf("%d | ", e.Line)
indent := strings.Repeat(" ", len(lnum)-2)
gray.Fprintf(w, "%s|\n", indent)
gray.Fprint(w, lnum)
fmt.Fprintln(w, line)
gray.Fprintf(w, "%s| ", indent)
green.Fprintln(w, e.getIndicator(line))
}
func (e *Error) getLine(source []byte) (string, bool) {
s := bufio.NewScanner(bytes.NewReader(source))
l := 0
for s.Scan() {
l++
if l == e.Line {
return s.Text(), true
}
}
return "", false
}
func (e *Error) getIndicator(line string) string {
if e.Column <= 0 {
return ""
}
start := e.Column - 1 // Column is 1-based
// Count width of non-space characters after '^' for underline
uw := 0
r := strings.NewReader(line[start:])
for {
c, s, err := r.ReadRune()
if err != nil || s == 0 || c == ' ' || c == '\t' || c == '\n' || c == '\r' {
break
}
uw += runewidth.RuneWidth(c)
}
if uw > 0 {
uw-- // Decrement for place for '^'
}
// Count width of spaces before '^'
sw := runewidth.StringWidth(line[:start])
return fmt.Sprintf("%s^%s", strings.Repeat(" ", sw), strings.Repeat("~", uw))
}
// ByErrorPosition is type for sort.Interface. It sorts errors slice in line and column order.
type ByErrorPosition []*Error
func (by ByErrorPosition) Len() int {
return len(by)
}
func (by ByErrorPosition) Less(i, j int) bool {
if by[i].Line == by[j].Line {
return by[i].Column < by[j].Column
}
return by[i].Line < by[j].Line
}
func (by ByErrorPosition) Swap(i, j int) {
by[i], by[j] = by[j], by[i]
}