-
-
Notifications
You must be signed in to change notification settings - Fork 140
/
error.go
179 lines (154 loc) · 4 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package core
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/quick"
"github.com/logrusorgru/aurora/v3"
)
type lineError struct {
content string
line int
span []int
}
// The following represent all possible errors generated by Vale. They are
// split into three groups:
//
// 100-level: Unexpected errors (such as filesystem failures).
//
// 200-level: User-introduced errors (such as bad configuration).
//
// 300-level: Environment errors (such as missing dependencies).
var (
// E200 indicates that the user has not created a configuration file
// (`.vale.ini`) in a discoverable location.
E200 = NewError(
"E200",
"Unable to locate a configuration file.",
"See https://docs.errata.ai/vale/config#search-process for more information.")
)
type errorCondition func(position int, line, target string) bool
func annotate(file []byte, target string, finder errorCondition) (lineError, error) {
var sb strings.Builder
scanner := bufio.NewScanner(bytes.NewBuffer(file))
context := lineError{span: []int{1, 1}}
idx := 1
for scanner.Scan() {
markup := scanner.Text()
plain := StripANSI(markup)
if idx-context.line > 2 && context.line != 0 {
break
} else if finder(idx, plain, target) && context.line == 0 {
context.line = idx
s := strings.Index(plain, target) + 1
context.span = []int{s, s + len(target)}
sb.WriteString(
fmt.Sprintf("\033[1;32m%4d\033[0m* %s\n", idx, markup))
} else {
sb.WriteString(
fmt.Sprintf("\033[1;32m%4d\033[0m %s\n", idx, markup))
}
idx++
}
if err := scanner.Err(); err != nil {
return lineError{}, err
}
lines := []string{}
for i, l := range strings.Split(sb.String(), "\n") {
if context.line-i < 3 {
lines = append(lines, l)
}
}
context.content = strings.Join(lines, "\n")
return context, nil
}
// NewError creates a colored error from the given information.
//
// The standard format is
//
// ```
// <code> [<context>] <title>
//
// <msg>
// ```
func NewError(code, title, msg string) error {
return fmt.Errorf(
"%s %s\n\n%s\n\n%s",
aurora.BgRed(code),
title,
msg,
aurora.Faint(aurora.Italic("Execution stopped with code 1.")),
)
}
// NewE100 creates a new, formatted "unexpected" error.
//
// Since E100 errors can occur anywhere, we include a "context" that makes it
// clear where exactly the error was generated.
func NewE100(context string, err error) error {
title := fmt.Sprintf("[%s] %s", context, "Runtime error")
return NewError("E100", title, err.Error())
}
// NewE201 creates a formatted user-generated error.
//
// 201 errors involve a specific configuration asset and should contain
// parsable location information on their last line of the form:
//
// <path>:<line>:<start>:<end>
func NewE201(msg, value, path string, finder errorCondition) error {
var sb bytes.Buffer
f, err := ioutil.ReadFile(path)
if err != nil {
return NewE100("NewE201", errors.New(msg))
}
lexer := lexers.Match(path)
if lexer == nil {
lexer = lexers.Fallback
}
err = quick.Highlight(
&sb,
string(f),
lexer.Config().Name,
"terminal256",
"monokai")
if err != nil {
return NewE100("NewE201/Highlight", err)
}
ctx, err := annotate(sb.Bytes(), value, finder)
if err != nil {
return NewE100("NewE201/annotate", err)
}
title := fmt.Sprintf(
"Invalid value provided [%s:%d:%d]:",
filepath.ToSlash(path),
ctx.line,
ctx.span[0])
return NewError(
"E201",
title,
fmt.Sprintf("%s\n%s", ctx.content, msg))
}
// NewE201FromTarget creates a new E201 error from a target string.
func NewE201FromTarget(msg, value, file string) error {
return NewE201(
msg,
value,
file,
func(position int, line, target string) bool {
return strings.Contains(line, target)
})
}
// NewE201FromPosition creates a new E201 error from an in-file location.
func NewE201FromPosition(msg, file string, goal int) error {
return NewE201(
msg,
"",
file,
func(position int, line, target string) bool {
return position == goal
})
}