a940c30903
The new "Nav" member on a zcl.File is an opaque object that can be populated by parsers with an object that supports certain interfaces that are not part of the main API but are useful for integration with editors and other tooling. As a first example of this, we replace the hack for getting context in the diagnostic package with a new ContextString interface, which can then be optionally implemented by a given parser to return a contextual string native to the source language.
164 lines
3.5 KiB
Go
164 lines
3.5 KiB
Go
package zcl
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
wordwrap "github.com/mitchellh/go-wordwrap"
|
|
)
|
|
|
|
type diagnosticTextWriter struct {
|
|
files map[string]*File
|
|
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, files map[string]*File, width uint, color bool) DiagnosticWriter {
|
|
return &diagnosticTextWriter{
|
|
files: files,
|
|
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 {
|
|
|
|
file := w.files[diag.Subject.Filename]
|
|
if file == nil || file.Bytes == nil {
|
|
fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line)
|
|
} else {
|
|
src := file.Bytes
|
|
r := bytes.NewReader(src)
|
|
sc := bufio.NewScanner(r)
|
|
sc.Split(bufio.ScanLines)
|
|
|
|
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
|
|
}
|
|
|
|
var contextLine string
|
|
if diag.Subject != nil {
|
|
contextLine = contextString(file, diag.Subject.Start.Byte)
|
|
if contextLine != "" {
|
|
contextLine = ", in " + contextLine
|
|
}
|
|
}
|
|
|
|
li := 1
|
|
var ls string
|
|
for sc.Scan() {
|
|
ls = sc.Text()
|
|
|
|
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
|
|
}
|
|
|
|
func contextString(file *File, offset int) string {
|
|
type contextStringer interface {
|
|
ContextString(offset int) string
|
|
}
|
|
|
|
if cser, ok := file.Nav.(contextStringer); ok {
|
|
return cser.ContextString(offset)
|
|
}
|
|
return ""
|
|
}
|