ca6c6b127c
The indication of specific characters in the source code that are in error is not yet implemented, but this gets at the main functionality of printing diagnostics.
153 lines
3.6 KiB
Go
153 lines
3.6 KiB
Go
package zcl
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
wordwrap "github.com/mitchellh/go-wordwrap"
|
|
)
|
|
|
|
type diagnosticTextWriter struct {
|
|
sources map[string][]byte
|
|
wr io.Writer
|
|
width uint
|
|
color bool
|
|
}
|
|
|
|
// NewDiagnosticTextWriter creates a DiagnosticWriter that writes diagnostics
|
|
// to the given writer as formatted text.
|
|
//
|
|
// It is designed to produce text appropriate to print in a monospaced font
|
|
// in a terminal of a particular width, or optionally with no width limit.
|
|
//
|
|
// The given width may be zero to disable word-wrapping of the detail text
|
|
// and truncation of source code snippets.
|
|
//
|
|
// If color is set to true, the output will include VT100 escape sequences to
|
|
// color-code the severity indicators. It is suggested to turn this off if
|
|
// the target writer is not a terminal.
|
|
func NewDiagnosticTextWriter(wr io.Writer, sources map[string][]byte, width uint, color bool) DiagnosticWriter {
|
|
return &diagnosticTextWriter{
|
|
sources: sources,
|
|
wr: wr,
|
|
width: width,
|
|
color: color,
|
|
}
|
|
}
|
|
|
|
func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
|
if diag == nil {
|
|
return errors.New("nil diagnostic")
|
|
}
|
|
|
|
var colorCode, resetCode string
|
|
if w.color {
|
|
switch diag.Severity {
|
|
case DiagError:
|
|
colorCode = "\x1b[31m"
|
|
case DiagWarning:
|
|
colorCode = "\x1b[33m"
|
|
}
|
|
resetCode = "\x1b[0m"
|
|
}
|
|
|
|
var severityStr string
|
|
switch diag.Severity {
|
|
case DiagError:
|
|
severityStr = "Error"
|
|
case DiagWarning:
|
|
severityStr = "Warning"
|
|
default:
|
|
// should never happen
|
|
severityStr = "???????"
|
|
}
|
|
|
|
fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary)
|
|
|
|
if diag.Subject != nil {
|
|
|
|
src := w.sources[diag.Subject.Filename]
|
|
if src == nil {
|
|
fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line)
|
|
} else {
|
|
r := bytes.NewReader(src)
|
|
sc := bufio.NewScanner(r)
|
|
sc.Split(bufio.ScanLines)
|
|
contextLine := ""
|
|
|
|
var startLine, endLine int
|
|
if diag.Context != nil {
|
|
startLine = diag.Context.Start.Line
|
|
endLine = diag.Context.End.Line
|
|
} else {
|
|
startLine = diag.Subject.Start.Line
|
|
endLine = diag.Subject.End.Line
|
|
}
|
|
|
|
li := 1
|
|
var ls string
|
|
for sc.Scan() {
|
|
ls = sc.Text()
|
|
if len(ls) > 0 && strings.Contains(ls, "{") && ls[0] != ' ' && ls[0] != '\t' && ls[0] != '#' && ls[0] != '/' && ls[0] != '{' {
|
|
// Keep track of the latest non-space-prefixed line we've
|
|
// seen, to use as context. This is a pretty sloppy way
|
|
// to do this, but it works well enough for most normal
|
|
// files. (In particular though, it doesn't work for JSON sources.)
|
|
contextLine = fmt.Sprintf(", in %s", strings.Replace(ls, " {", "", 1))
|
|
}
|
|
|
|
if li == startLine {
|
|
break
|
|
}
|
|
li++
|
|
}
|
|
|
|
fmt.Fprintf(w.wr, " on %s line %d%s:\n", diag.Subject.Filename, diag.Subject.Start.Line, contextLine)
|
|
|
|
// TODO: Generate markers for the specific characters that are in the Context and Subject ranges.
|
|
// For now, we just print out the lines.
|
|
|
|
fmt.Fprintf(w.wr, "%4d: %s\n", li, ls)
|
|
|
|
if endLine > li {
|
|
for sc.Scan() {
|
|
ls = sc.Text()
|
|
li++
|
|
|
|
fmt.Fprintf(w.wr, "%4d: %s\n", li, ls)
|
|
|
|
if li == endLine {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
w.wr.Write([]byte{'\n'})
|
|
}
|
|
}
|
|
|
|
if diag.Detail != "" {
|
|
detail := diag.Detail
|
|
if w.width != 0 {
|
|
detail = wordwrap.WrapString(detail, w.width)
|
|
}
|
|
fmt.Fprintf(w.wr, "%s\n\n", detail)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *diagnosticTextWriter) WriteDiagnostics(diags Diagnostics) error {
|
|
for _, diag := range diags {
|
|
err := w.WriteDiagnostic(diag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|