@@ -10,6 +10,7 @@ import (
1010 "go/types"
1111 "io"
1212 "path/filepath"
13+ "reflect"
1314 "sort"
1415 "strings"
1516
@@ -23,6 +24,11 @@ import (
2324type Diagnostic struct {
2425 Pos token.Position
2526 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
2632}
2733
2834// One or multiple errors of a particular package.
@@ -114,12 +120,22 @@ func createPackageDiagnostic(err error) PackageDiagnostic {
114120func createDiagnostics (err error ) []Diagnostic {
115121 switch err := err .(type ) {
116122 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 ()))
122137 }
138+ return []Diagnostic {diag }
123139 case scanner.Error :
124140 return []Diagnostic {
125141 {
@@ -188,25 +204,29 @@ func (diag Diagnostic) WriteTo(w io.Writer, wd string) {
188204 fmt .Fprintln (w , diag .Msg )
189205 return
190206 }
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 )
198208 fmt .Fprintf (w , "%s: %s\n " , pos , diag .Msg )
199209}
200210
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.
204216 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
206223 }
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
210230 }
211- return relpath
231+ return pos
212232}
0 commit comments