Ctrl K
ComunidadGuías…

pkg/errlist/errlist.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
	}
}