Skip to content

Commit de53264

Browse files
aykevldeadprogram
authored andcommitted
main: add StartPos and EndPos to -json build output
This is useful for the TinyGo Playground: with this change it can show the error span like in an IDE, instead of just the (single) error position. Errors become a bit more readable as a result.
1 parent c2765e9 commit de53264

File tree

2 files changed

+55
-28
lines changed

2 files changed

+55
-28
lines changed

diagnostics/diagnostics.go

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (
2324
type 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 {
114120
func 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
}

main.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,8 @@ func printBuildOutput(err error, jsonDiagnostics bool) {
13981398
ImportPath string
13991399
Action string
14001400
Output string `json:",omitempty"`
1401+
StartPos string `json:",omitempty"` // non-standard
1402+
EndPos string `json:",omitempty"` // non-standard
14011403
}
14021404

14031405
for _, diags := range diagnostics.CreateDiagnostics(err) {
@@ -1407,28 +1409,33 @@ func printBuildOutput(err error, jsonDiagnostics bool) {
14071409
Action: "build-output",
14081410
Output: "# " + diags.ImportPath + "\n",
14091411
})
1410-
os.Stdout.Write(output)
1411-
os.Stdout.Write([]byte{'\n'})
1412+
os.Stdout.Write(append(output, '\n'))
14121413
}
14131414
for _, diag := range diags.Diagnostics {
14141415
w := &bytes.Buffer{}
14151416
diag.WriteTo(w, workingDir)
1416-
output, _ := json.Marshal(jsonDiagnosticOutput{
1417+
data := jsonDiagnosticOutput{
14171418
ImportPath: diags.ImportPath,
14181419
Action: "build-output",
14191420
Output: w.String(),
1420-
})
1421-
os.Stdout.Write(output)
1422-
os.Stdout.Write([]byte{'\n'})
1421+
}
1422+
if diag.StartPos.IsValid() && diag.EndPos.IsValid() {
1423+
// Include the non-standard StartPos/EndPos values. These
1424+
// are useful for the TinyGo Playground to show better error
1425+
// messages.
1426+
data.StartPos = diagnostics.RelativePosition(diag.StartPos, workingDir).String()
1427+
data.EndPos = diagnostics.RelativePosition(diag.EndPos, workingDir).String()
1428+
}
1429+
output, _ := json.Marshal(data)
1430+
os.Stdout.Write(append(output, '\n'))
14231431
}
14241432

14251433
// Emit the "Action":"build-fail" JSON.
14261434
output, _ := json.Marshal(jsonDiagnosticOutput{
14271435
ImportPath: diags.ImportPath,
14281436
Action: "build-fail",
14291437
})
1430-
os.Stdout.Write(output)
1431-
os.Stdout.Write([]byte{'\n'})
1438+
os.Stdout.Write(append(output, '\n'))
14321439
}
14331440
os.Exit(1)
14341441
}

0 commit comments

Comments
 (0)