blob: 745604f518e3c36b09345612c0ccdf8c36885548 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package diagnostics provides utilities to collect diagnostics for lint.
package diagnostics
import (
"fmt"
"sort"
"strings"
"sync"
"go.chromium.org/luci/common/errors"
)
// Result contains diagnostic messages from lint.
type Result struct {
Errors errors.MultiError
prefixes []string
}
// PushContext adds context to be prepended to all diagnostic messages.
//
// Contexts can be stacked by calling PushContext() repeatedly.
// Returns a function to pop the pushed context.
func (r *Result) PushContext(c string) (popContext func()) {
r.prefixes = append(r.prefixes, fmt.Sprintf("%s: ", c))
// Stay true to our API -- returned function can only pop one context.
// We still don't deal with the possibility that the returned closers from
// multiple PushContext() calls can be called in arbitrary order, and will
// have the same effect.
o := sync.Once{}
return func() {
o.Do(r.dropContext)
}
}
// Merge merges another result into the current result.
//
// Diagnostic messages from the incoming Result are prefixed with the current
// Result's context.
// Context from the incoming Result is ignored.
func (r *Result) Merge(o Result) {
for _, err := range o.Errors {
r.AppendError(err.Error())
}
}
// AppendError appends an error to result, prefixed with current context.
func (r *Result) AppendError(f string, args ...interface{}) {
r.Errors = append(r.Errors, errors.New(fmt.Sprintf(r.prefixWithContext(f), args...)))
}
// IsValid returns false if the result contains any validation errors, true
// otherwise.
func (r *Result) IsValid() bool {
return len(r.Errors) == 0
}
// Display returns a user-friendly display of diagnostics from a Result.
func (r *Result) Display() []string {
ss := []string{}
if r.Errors != nil {
ss = append(ss, "## Errors ##")
for _, err := range r.Errors {
ss = append(ss, err.Error())
}
}
sort.Strings(ss)
return breakAndIndentMultiLine(ss)
}
func (r *Result) dropContext() {
r.prefixes = r.prefixes[:len(r.prefixes)-1]
}
func (r *Result) prefixWithContext(s string) string {
return strings.Join(r.prefixes, "") + s
}
func breakAndIndentMultiLine(ss []string) []string {
ret := make([]string, 0, len(ss))
for _, s := range ss {
ps := strings.Split(s, "\n")
// strings.Split() returns at least one element, even for empty input.
ret = append(ret, ps[0])
for _, p := range ps[1:] {
ret = append(ret, fmt.Sprintf(" %s", p))
}
}
return ret
}