@@ -10,6 +10,7 @@ import (
10
10
"go/types"
11
11
"io"
12
12
"path/filepath"
13
+ "reflect"
13
14
"sort"
14
15
"strings"
15
16
@@ -23,6 +24,11 @@ import (
23
24
type Diagnostic struct {
24
25
Pos token.Position
25
26
Msg string
27
+
28
+ // Start and end position, if available. For many errors these positions are
29
+ // not available, but for some they are.
30
+ StartPos token.Position
31
+ EndPos token.Position
26
32
}
27
33
28
34
// One or multiple errors of a particular package.
@@ -114,12 +120,22 @@ func createPackageDiagnostic(err error) PackageDiagnostic {
114
120
func createDiagnostics (err error ) []Diagnostic {
115
121
switch err := err .(type ) {
116
122
case types.Error :
117
- return []Diagnostic {
118
- {
119
- Pos : err .Fset .Position (err .Pos ),
120
- Msg : err .Msg ,
121
- },
123
+ diag := Diagnostic {
124
+ Pos : err .Fset .Position (err .Pos ),
125
+ Msg : err .Msg ,
126
+ }
127
+ // There is a special unexported API since Go 1.16 that provides the
128
+ // range (start and end position) where the type error exists.
129
+ // There is no promise of backwards compatibility in future Go versions
130
+ // so we have to be extra careful here to be resilient.
131
+ v := reflect .ValueOf (err )
132
+ start := v .FieldByName ("go116start" )
133
+ end := v .FieldByName ("go116end" )
134
+ if start .IsValid () && end .IsValid () && start .Int () != end .Int () {
135
+ diag .StartPos = err .Fset .Position (token .Pos (start .Int ()))
136
+ diag .EndPos = err .Fset .Position (token .Pos (end .Int ()))
122
137
}
138
+ return []Diagnostic {diag }
123
139
case scanner.Error :
124
140
return []Diagnostic {
125
141
{
@@ -188,25 +204,29 @@ func (diag Diagnostic) WriteTo(w io.Writer, wd string) {
188
204
fmt .Fprintln (w , diag .Msg )
189
205
return
190
206
}
191
- pos := diag .Pos // make a copy
192
- if ! strings .HasPrefix (pos .Filename , filepath .Join (goenv .Get ("GOROOT" ), "src" )) && ! strings .HasPrefix (pos .Filename , filepath .Join (goenv .Get ("TINYGOROOT" ), "src" )) {
193
- // This file is not from the standard library (either the GOROOT or the
194
- // TINYGOROOT). Make the path relative, for easier reading. Ignore any
195
- // errors in the process (falling back to the absolute path).
196
- pos .Filename = tryToMakePathRelative (pos .Filename , wd )
197
- }
207
+ pos := RelativePosition (diag .Pos , wd )
198
208
fmt .Fprintf (w , "%s: %s\n " , pos , diag .Msg )
199
209
}
200
210
201
- // try to make the path relative to the current working directory. If any error
202
- // occurs, this error is ignored and the absolute path is returned instead.
203
- func tryToMakePathRelative (dir , wd string ) string {
211
+ // Convert the position in pos (assumed to have an absolute path) into a
212
+ // relative path if possible. Paths inside GOROOT/TINYGOROOT will remain
213
+ // absolute.
214
+ func RelativePosition (pos token.Position , wd string ) token.Position {
215
+ // Check whether we even have a working directory.
204
216
if wd == "" {
205
- return dir // working directory not found
217
+ return pos
218
+ }
219
+
220
+ // Paths inside GOROOT should be printed in full.
221
+ if strings .HasPrefix (pos .Filename , filepath .Join (goenv .Get ("GOROOT" ), "src" )) || strings .HasPrefix (pos .Filename , filepath .Join (goenv .Get ("TINYGOROOT" ), "src" )) {
222
+ return pos
206
223
}
207
- relpath , err := filepath .Rel (wd , dir )
208
- if err != nil {
209
- return dir
224
+
225
+ // Make the path relative, for easier reading. Ignore any errors in the
226
+ // process (falling back to the absolute path).
227
+ relpath , err := filepath .Rel (wd , pos .Filename )
228
+ if err == nil {
229
+ pos .Filename = relpath
210
230
}
211
- return relpath
231
+ return pos
212
232
}
0 commit comments