pkg/errlist/errlist.go
Rama main · réplica de lectura del repositorio original.
package errlist
import (
"encoding/json"
"fmt"
"go/scanner"
"go/token"
"io"
"path/filepath"
"strings"
"encr.dev/pkg/errinsrc"
"encr.dev/pkg/errinsrc/srcerrors"
daemonpb "encr.dev/proto/encore/daemon"
)
// Verbose controls whether the error list prints all errors
// or just the what MaxErrorsToPrint is set to
var Verbose = false
// MaxErrorsToPrint is the maximum number of errors to print
// if Verbose is false
var MaxErrorsToPrint = 1
type List struct {
List errinsrc.List `json:"list,omitempty"`
fset *token.FileSet
}
var _ errinsrc.ErrorList = (*List)(nil)
func New(fset *token.FileSet) *List {
return &List{fset: fset}
}
// Convert attempts to convert known error types into an error list
// if it can't it returns nil
func Convert(err error) *List {
switch err := err.(type) {
case *List:
return err
case *errinsrc.ErrInSrc:
l := New(nil)
l.List = []*errinsrc.ErrInSrc{err}
return l
case errinsrc.ErrorList:
l := New(nil)
l.List = err.ErrorList()
return l
default:
return nil
}
}
// Report is a function that allows you to report an error into
// this list, without having to check for nil.
//
// This function only supports error of types:
// - *List
// - *errinsrc.ErrInSrc
// - *scanner.Error
//
// If too many errors have been reported it panics
// with a Bailout value to abort processing.
// Use HandleBailout to conveniently handle this.
func (l *List) Report(err error) {
if err == nil {
return
}
var errToAdd *errinsrc.ErrInSrc
switch err := (err).(type) {
case *errinsrc.ErrInSrc:
// the base type we expect
errToAdd = err
case *scanner.Error:
// errors directly from the Go parser
errToAdd = srcerrors.GenericGoParserError(err)
case scanner.ErrorList:
for _, e := range err {
l.Report(e)
}
return
case *List:
// If it's a different list, then merge it in
if err != l {
for _, e := range err.ErrorList() {
l.Report(e)
}
}
return
case errinsrc.ErrorList:
// either another errlist or a list from `srcerrors`
for _, e := range err.ErrorList() {
l.Report(e)
}
return
default:
panic(fmt.Sprintf("unsupported type %T being reported to errlist.List", err))
}
// Skip adding this error if it's on the same line as another error
// we've already reported, since it's probably a spurious error caused
// by the first one.
for _, e := range l.List {
if errToAdd.OnSameLine(e) {
return
}
}
l.List = append(l.List, errToAdd)
if len(l.List) > 10 {
l.Abort()
}
}
// Add adds an error to the list.
//
// If too many errors have been added it panics
// with a Bailout value to abort processing.
// Use HandleBailout to conveniently handle this.
//
// Deprecated: use Report instead
func (l *List) Add(pos token.Pos, msg string) {
pp := l.fset.Position(pos)
l.Report(&scanner.Error{
Pos: pp,
Msg: msg,
})
}
// Addf is equivalent to Add(pos, fmt.Sprintf(format, args...))
//
// Deprecated: use Report instead
func (l *List) Addf(pos token.Pos, format string, args ...interface{}) {
l.Add(pos, fmt.Sprintf(format, args...))
}
// AddRaw adds a raw *scanner.Error to the list.
//
// If too many errors have been added it panics
// with a Bailout value to abort processing.
// Use HandleBailout to conveniently handle this.
//
// Deprecated: use Report instead
func (l *List) AddRaw(err *scanner.Error) {
l.Report(err)
}
// Merge merges another list into this one.
// The token.FileSet in use must be the same one as this one,
// or else it panics.
//
// Deprecated: use Report instead
func (l *List) Merge(other *List) {
if other.fset != l.fset {
panic("errlist: cannot merge lists with different *token.FileSets")
}
l.List = append(l.List, other.List...)
}
// Err returns an error equivalent to this error list.
// If the list is empty, Err returns nil.
func (l *List) Err() error {
if len(l.List) == 0 {
return nil
}
return l
}
// Error implements the error interface.
func (l *List) Error() string {
var b strings.Builder
if Verbose {
for _, err := range l.List {
b.WriteString(err.Error())
}
} else {
if len(l.List) == 0 {
b.WriteString("no errors")
} else {
for i, err := range l.List {
b.WriteString(err.Error())
if i >= MaxErrorsToPrint-1 {
break
}
}
if len(l.List) > MaxErrorsToPrint {
b.WriteString(fmt.Sprintf("And %d more errors", len(l.List)-MaxErrorsToPrint))
}
}
}
return b.String()
}
func (l *List) ErrorList() []*errinsrc.ErrInSrc {
return l.List
}
// MakeRelative rewrites the errors by making filenames within the
// app root relative to the relwd (which must be a relative path
// within the root).
func (l *List) MakeRelative(root, relwd string) {
wdroot := filepath.Join(root, relwd)
for _, e := range l.List {
for _, loc := range e.Params.Locations {
if loc.File != nil {
fn := loc.File.RelPath
if strings.HasPrefix(fn, root) {
if rel, err := filepath.Rel(wdroot, fn); err == nil {
loc.File.RelPath = rel
}
}
}
}
}
}
// HandleBailout handles bailouts raised by (*List).Add and family
// when too many errors have been found.
func (l *List) HandleBailout(err *error) {
if e := recover(); e != nil {
if b, ok := e.(errinsrc.Bailout); ok {
*err = b.List
} else {
panic(e)
}
}
}
func (l *List) Len() int {
return len(l.List)
}
// Abort aborts early if there is an error in the list.
func (l *List) Abort() {
errinsrc.Panic(l)
}
// SendToStream sends a GRPC command with this
// full errlist
//
// If l is nil or empty, it sends a nil command
// allowing the client to know that there are no
// longer an error present
func (l *List) SendToStream(stream interface {
Send(*daemonpb.CommandMessage) error
}) error {
var bytes []byte
if l != nil && len(l.List) > 0 {
var err error
bytes, err = json.Marshal(l)
if err != nil {
panic("unable to marshal error list")
}
}
return stream.Send(
&daemonpb.CommandMessage{
Msg: &daemonpb.CommandMessage_Errors{
Errors: &daemonpb.CommandDisplayErrors{
Errinsrc: bytes,
},
},
},
)
}
// Print is a utility function that prints a list of errors to w,
// one error per line, if the err parameter is an errorList. Otherwise
// it prints the err string.
func Print(w io.Writer, err error) {
if l, ok := err.(*List); ok {
for _, e := range l.List {
fmt.Fprintf(w, "%s\n", e)
}
} else if err != nil {
fmt.Fprintf(w, "%s\n", err)
}
}