Skip to content

Commit

Permalink
Implement workspace diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
kralicky committed Jun 26, 2023
1 parent e700961 commit 0299b46
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 61 deletions.
68 changes: 48 additions & 20 deletions protols/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,39 +625,67 @@ func (c *Cache) getMapper(uri span.URI) (*protocol.Mapper, error) {
return c.compiler.overlay.Get(c.filePathsByURI[uri])
}

func (c *Cache) ComputeDiagnosticReports(uri span.URI) ([]*protocol.Diagnostic, error) {
func (c *Cache) ComputeDiagnosticReports(uri span.URI, prevResultId string) ([]protocol.Diagnostic, protocol.DocumentDiagnosticReportKind, string, error) {
c.resultsMu.Lock()
defer c.resultsMu.Unlock()
rawReports, found := c.diagHandler.GetDiagnosticsForPath(c.filePathsByURI[uri])
if !found {
return nil, nil // no reports
var maybePrevResultId []string
if prevResultId != "" {
maybePrevResultId = append(maybePrevResultId, prevResultId)
}
mapper, err := c.getMapper(uri)
if err != nil {
return nil, err
rawReports, resultId, unchanged := c.diagHandler.GetDiagnosticsForPath(c.filePathsByURI[uri], maybePrevResultId...)
if unchanged {
return nil, protocol.DiagnosticUnchanged, resultId, nil
}
protocolReports := c.toProtocolDiagnostics(rawReports)

return protocolReports, protocol.DiagnosticFull, resultId, nil
}

// convert to protocol reports
var reports []*protocol.Diagnostic
func (c *Cache) toProtocolDiagnostics(rawReports []*ProtoDiagnostic) []protocol.Diagnostic {
var reports []protocol.Diagnostic
for _, rawReport := range rawReports {
rng, err := mapper.OffsetRange(rawReport.Pos.Start().Offset, rawReport.Pos.End().Offset+1)
if err != nil {
c.lg.With(
zap.String("file", string(uri)),
zap.Error(err),
).Debug("failed to map range")
continue
}
reports = append(reports, &protocol.Diagnostic{
Range: rng,
reports = append(reports, protocol.Diagnostic{
Range: toRange(rawReport.Pos),
Severity: rawReport.Severity,
Message: rawReport.Error.Error(),
Tags: rawReport.Tags,
Source: "protols",
})
}
return reports
}

type workspaceDiagnosticCallbackFunc = func(uri span.URI, reports []protocol.Diagnostic, kind protocol.DocumentDiagnosticReportKind, resultId string)

return reports, nil
func (c *Cache) ComputeWorkspaceDiagnosticReports(ctx context.Context, previousResultIds []protocol.PreviousResultID, callback workspaceDiagnosticCallbackFunc) bool {
var prevResultMapByPath map[string]string
return c.diagHandler.MaybeRange(func() {
prevResultMapByPath = make(map[string]string, len(previousResultIds))
for _, prevResult := range previousResultIds {
if p, err := c.URIToPath(prevResult.URI.SpanURI()); err == nil {
prevResultMapByPath[p] = prevResult.Value
}
}
}, func(s string, dl *DiagnosticList) bool {
var maybePrevResultId []string
prevResultId, ok := prevResultMapByPath[s]
if ok {
maybePrevResultId = append(maybePrevResultId, prevResultId)
}
uri, err := c.PathToURI(s)
if err != nil {
return true // ???
}
rawResults, resultId, unchanged := dl.Get(maybePrevResultId...)
if unchanged {
callback(uri, []protocol.Diagnostic{}, protocol.DiagnosticUnchanged, resultId)
} else {
protocolResults := c.toProtocolDiagnostics(rawResults)

callback(uri, protocolResults, protocol.DiagnosticFull, resultId)
}
return true
})
}

func (c *Cache) ComputeDocumentLinks(doc protocol.TextDocumentIdentifier) ([]protocol.DocumentLink, error) {
Expand Down
102 changes: 79 additions & 23 deletions protols/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"fmt"
"os"
"sync"
"time"

"github.com/bufbuild/protocompile/ast"
"github.com/bufbuild/protocompile/linker"
"github.com/bufbuild/protocompile/reporter"
gsync "github.com/kralicky/gpkg/sync"
"go.uber.org/atomic"
"golang.org/x/tools/gopls/pkg/lsp/protocol"
)

Expand All @@ -21,21 +24,56 @@ type ProtoDiagnostic struct {

func NewDiagnosticHandler() *DiagnosticHandler {
return &DiagnosticHandler{
diagnostics: make(map[string][]*ProtoDiagnostic),
modified: atomic.NewBool(false),
}
}

type DiagnosticList struct {
lock sync.RWMutex
Diagnostics []*ProtoDiagnostic
ResultId string
}

func (dl *DiagnosticList) Add(d *ProtoDiagnostic) {
dl.lock.Lock()
defer dl.lock.Unlock()
dl.Diagnostics = append(dl.Diagnostics, d)
dl.resetResultId()
}

func (dl *DiagnosticList) Get(prevResultId ...string) (diagnostics []*ProtoDiagnostic, resultId string, unchanged bool) {
dl.lock.RLock()
defer dl.lock.RUnlock()
if len(prevResultId) == 1 && dl.ResultId == prevResultId[0] {
return []*ProtoDiagnostic{}, dl.ResultId, true
}
return dl.Diagnostics, dl.ResultId, false
}

func (dl *DiagnosticList) Clear() []*ProtoDiagnostic {
dl.lock.Lock()
defer dl.lock.Unlock()
dl.Diagnostics = []*ProtoDiagnostic{}
dl.resetResultId()
return dl.Diagnostics
}

// requires lock to be held in write mode
func (dl *DiagnosticList) resetResultId() {
dl.ResultId = time.Now().Format(time.RFC3339Nano)
}

type DiagnosticHandler struct {
diagnosticsMu sync.Mutex
diagnostics map[string][]*ProtoDiagnostic
diagnostics gsync.Map[string, *DiagnosticList]
modified *atomic.Bool
}

func tagsForError(err error) []protocol.DiagnosticTag {
switch errors.Unwrap(err).(type) {
case linker.ErrorUnusedImport:
return []protocol.DiagnosticTag{protocol.Unnecessary}
default:
return nil
return []protocol.DiagnosticTag{}
}
}

Expand All @@ -46,19 +84,22 @@ func (dr *DiagnosticHandler) HandleError(err reporter.ErrorWithPos) error {

fmt.Fprintf(os.Stderr, "[diagnostic] error: %s\n", err.Error())

dr.diagnosticsMu.Lock()
defer dr.diagnosticsMu.Unlock()

pos := err.GetPosition()
filename := pos.Start().Filename

dr.diagnostics[filename] = append(dr.diagnostics[filename], &ProtoDiagnostic{
empty := DiagnosticList{
Diagnostics: []*ProtoDiagnostic{},
}
dl, _ := dr.diagnostics.LoadOrStore(filename, &empty)
dl.Add(&ProtoDiagnostic{
Pos: pos,
Severity: protocol.SeverityError,
Error: err.Unwrap(),
Tags: tagsForError(err),
})

dr.modified.CompareAndSwap(false, true)

return nil // allow the compiler to continue
}

Expand All @@ -69,35 +110,50 @@ func (dr *DiagnosticHandler) HandleWarning(err reporter.ErrorWithPos) {

fmt.Fprintf(os.Stderr, "[diagnostic] error: %s\n", err.Error())

dr.diagnosticsMu.Lock()
defer dr.diagnosticsMu.Unlock()

pos := err.GetPosition()
filename := pos.Start().Filename

dr.diagnostics[filename] = append(dr.diagnostics[filename], &ProtoDiagnostic{
empty := DiagnosticList{
Diagnostics: []*ProtoDiagnostic{},
}
dl, _ := dr.diagnostics.LoadOrStore(filename, &empty)
dl.Add(&ProtoDiagnostic{
Pos: pos,
Severity: protocol.SeverityWarning,
Error: err.Unwrap(),
Tags: tagsForError(err),
})
}

func (dr *DiagnosticHandler) GetDiagnosticsForPath(path string) ([]*ProtoDiagnostic, bool) {
dr.diagnosticsMu.Lock()
defer dr.diagnosticsMu.Unlock()
dr.modified.CompareAndSwap(false, true)
}

res, ok := dr.diagnostics[path]
func (dr *DiagnosticHandler) GetDiagnosticsForPath(path string, prevResultId ...string) ([]*ProtoDiagnostic, string, bool) {
dl, ok := dr.diagnostics.Load(path)
if !ok {
return []*ProtoDiagnostic{}, "", false
}
return dl.Get(prevResultId...)

fmt.Printf("[diagnostic] querying diagnostics for %s: (%d results)\n", path, len(res))
return res, ok
// fmt.Printf("[diagnostic] querying diagnostics for %s: (%d results)\n", path, len(res))
// return res, ok
}

func (dr *DiagnosticHandler) ClearDiagnosticsForPath(path string) {
dr.diagnosticsMu.Lock()
defer dr.diagnosticsMu.Unlock()
dl, ok := dr.diagnostics.Load(path)
if !ok {
return
}
dl.Clear()

fmt.Printf("[diagnostic] clearing %d diagnostics for %s\n", len(dr.diagnostics[path]), path)
// fmt.Printf("[diagnostic] clearing %d diagnostics for %s\n", len(dr.diagnostics[path]), path)

delete(dr.diagnostics, path)
}

func (dr *DiagnosticHandler) MaybeRange(setup func(), fn func(string, *DiagnosticList) bool) bool {
if dr.modified.CompareAndSwap(true, false) {
setup()
dr.diagnostics.Range(fn)
return true
}
return false
}
2 changes: 2 additions & 0 deletions protols/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,8 @@ func columnFormatElements[T ast.Node](f *formatter, elems []T) {
field.typeName, _ = io.ReadAll(colBuf) // these column names don't exactly match up with the others
fclone.writeLineEnd(elem.Val)
field.lineEnd, _ = io.ReadAll(colBuf)
case *ast.OptionNode:
// TODO

default:
panic(fmt.Sprintf("column formatting not implemented for element type %T", elem))
Expand Down
Loading

0 comments on commit 0299b46

Please sign in to comment.