Skip to content

Commit

Permalink
fix(internal/lsp): 修正 textDocument/publishDiagnostic 行为错误
Browse files Browse the repository at this point in the history
update #56
  • Loading branch information
caixw committed Aug 24, 2020
1 parent ede8665 commit dfe1133
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 76 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
### Fixed

- 修正同一个服务可以有多个相同接口的 BUG;
- 当用户第一次打开项目时,不会发送错误通知给用户的错误;
- 修正 textDocument/publishDiagnostics 的行为错误;

## [v7.1.0]

Expand Down
43 changes: 24 additions & 19 deletions internal/lsp/folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,20 @@ type folder struct {
parsedMux sync.RWMutex

// 保存着错误和警告的信息
errors, warns []*core.Error
diagnostics map[core.URI]*protocol.PublishDiagnosticsParams
}

func (f *folder) close() {
f.srv.windowLogLogMessage(locale.CloseLSPFolder, f.Name)
f.errors = f.errors[:0]
f.warns = f.warns[:0]

// 清空所有的诊断信息
for uri := range f.diagnostics {
p := protocol.NewPublishDiagnosticsParams(uri)
if err := f.srv.Notify("textDocument/publishDiagnostics", p); err != nil {
f.srv.erro.Println(err)
}
}

f.h.Stop()
}

Expand All @@ -46,25 +53,18 @@ func (f *folder) messageHandler(msg *core.Message) {
return
}

switch msg.Type {
case core.Erro:
cnt := sliceutil.Count(f.errors, func(i int) bool {
return f.errors[i].Location.Equal(err.Location)
if p, found := f.diagnostics[err.Location.URI]; found && p != nil {
cnt := sliceutil.Count(p.Diagnostics, func(i int) bool {
return p.Diagnostics[i].Range.Equal(err.Location.Range)
})
if cnt == 0 {
f.errors = append(f.errors, err)
p.AppendDiagnostic(err, msg.Type)
}
case core.Warn:
cnt := sliceutil.Count(f.warns, func(i int) bool {
return f.warns[i].Location.Equal(err.Location)
})
if cnt == 0 {
f.warns = append(f.warns, err)
}
case core.Succ, core.Info: // 仅处理错误和警告
default:
panic("unreached")
return
}
p := protocol.NewPublishDiagnosticsParams(err.Location.URI)
p.AppendDiagnostic(err, msg.Type)
f.diagnostics[err.Location.URI] = p
}

func (s *server) appendFolders(folders ...protocol.WorkspaceFolder) {
Expand All @@ -82,6 +82,7 @@ func (s *server) openFolder(f protocol.WorkspaceFolder) (ff *folder) {
WorkspaceFolder: f,
doc: &ast.APIDoc{},
srv: s,
diagnostics: make(map[core.URI]*protocol.PublishDiagnosticsParams, 5),
}
ff.h = core.NewMessageHandler(ff.messageHandler)

Expand All @@ -103,7 +104,11 @@ func (s *server) openFolder(f protocol.WorkspaceFolder) (ff *folder) {
build.ParseInputs(blocks, ff.h, ff.cfg.Inputs...)
})

if err := s.apidocOutline(ff); err != nil {
if err = s.apidocOutline(ff); err != nil {
s.printErr(err)
}

if err = s.textDocumentPublishDiagnostics(ff); err != nil {
s.printErr(err)
}

Expand Down
30 changes: 17 additions & 13 deletions internal/lsp/folder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,34 @@ import (
"github.com/issue9/assert"

"github.com/caixw/apidoc/v7/core"
"github.com/caixw/apidoc/v7/internal/locale"
"github.com/caixw/apidoc/v7/internal/lsp/protocol"
)

func TestFolder_messageHandler(t *testing.T) {
a := assert.New(t)

s := &server{erro: log.New(ioutil.Discard, "", 0)}
f := &folder{srv: s}
f := &folder{srv: s, diagnostics: map[core.URI]*protocol.PublishDiagnosticsParams{}}
f.messageHandler(&core.Message{Type: core.Erro, Message: "abc"})
a.Empty(f.errors).Empty(f.warns)
a.Empty(f.diagnostics)

f = &folder{srv: s}
f.messageHandler(&core.Message{Type: core.Erro, Message: &core.Error{Location: core.Location{URI: "uri"}}})
a.Empty(f.warns).Equal(1, len(f.errors))
err := locale.NewError(locale.ErrInvalidUTF8Character)

f = &folder{srv: s, diagnostics: map[core.URI]*protocol.PublishDiagnosticsParams{}}
f.messageHandler(&core.Message{Type: core.Erro, Message: &core.Error{Location: core.Location{URI: "uri"}, Err: err}})
a.Equal(1, len(f.diagnostics))
// 相同的错误,不会再次添加
f.messageHandler(&core.Message{Type: core.Erro, Message: &core.Error{Location: core.Location{URI: "uri"}}})
a.Empty(f.warns).Equal(1, len(f.errors))
f.messageHandler(&core.Message{Type: core.Erro, Message: &core.Error{Location: core.Location{URI: "uri"}, Err: err}})
a.Equal(1, len(f.diagnostics))

f = &folder{srv: s}
f.messageHandler(&core.Message{Type: core.Warn, Message: &core.Error{Location: core.Location{URI: "uri"}}})
a.Equal(1, len(f.warns)).Empty(f.errors)
f.messageHandler(&core.Message{Type: core.Warn, Message: &core.Error{Location: core.Location{URI: "uri"}}})
a.Equal(1, len(f.warns)).Empty(f.errors)
f = &folder{srv: s, diagnostics: map[core.URI]*protocol.PublishDiagnosticsParams{}}
f.messageHandler(&core.Message{Type: core.Warn, Message: &core.Error{Location: core.Location{URI: "uri"}, Err: err}})
a.Equal(1, len(f.diagnostics))
f.messageHandler(&core.Message{Type: core.Warn, Message: &core.Error{Location: core.Location{URI: "uri"}, Err: err}})
a.Equal(1, len(f.diagnostics))

f = &folder{srv: s}
f = &folder{srv: s, diagnostics: map[core.URI]*protocol.PublishDiagnosticsParams{}}
a.PanicString(func() {
f.messageHandler(&core.Message{Message: &core.Error{}, Type: -100})
}, "unreached")
Expand Down
2 changes: 1 addition & 1 deletion internal/lsp/protocol/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ type CompletionItem struct {
Detail string `json:"detail,omitempty"`

// A human-readable string that represents a doc-comment.
Documentation MarkupContent `json:"documentation,omitempty"`
Documentation *MarkupContent `json:"documentation,omitempty"`

// Select this item when showing.
//
Expand Down
27 changes: 27 additions & 0 deletions internal/lsp/protocol/completion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT

package protocol

import (
"encoding/json"
"testing"

"github.com/issue9/assert"
)

func TestCompletionList_MarshalJSON(t *testing.T) {
a := assert.New(t)

list := &CompletionList{}
data, err := json.Marshal(list)
a.NotError(err).Equal(string(data), "null")

list.IsIncomplete = true
list.Items = make([]CompletionItem, 0)
data, err = json.Marshal(list)
a.NotError(err).Equal(string(data), "null")

list.Items = append(list.Items, CompletionItem{})
data, err = json.Marshal(list)
a.NotError(err).Equal(string(data), `{"isIncomplete":true,"items":[{"label":""}]}`)
}
40 changes: 32 additions & 8 deletions internal/lsp/protocol/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ type PublishDiagnosticsParams struct {
// The URI for which diagnostic information is reported.
URI core.URI `json:"uri"`

// Optional the version number of the document the diagnostics are published for.
//
// @since 3.15.0
Version int `json:"version,omitempty"`

// An array of diagnostic information items.
Diagnostics []Diagnostic `json:"diagnostics"`
}
Expand Down Expand Up @@ -118,8 +113,31 @@ var typeTagsMap = map[core.ErrorType]DiagnosticTag{
core.ErrorTypeUnused: DiagnosticTagUnnecessary,
}

// BuildDiagnostic 根据 core.Error 生成 Diagnostic 数据
func BuildDiagnostic(err *core.Error, severity DiagnosticSeverity) Diagnostic {
// NewPublishDiagnosticsParams 声明空的 PublishDiagnosticsParams 对象
func NewPublishDiagnosticsParams(uri core.URI) *PublishDiagnosticsParams {
return &PublishDiagnosticsParams{
URI: uri,
Diagnostics: []Diagnostic{},
}
}

// AppendDiagnostic 将 core.Message 添加至诊断数据
func (p *PublishDiagnosticsParams) AppendDiagnostic(err *core.Error, msgType core.MessageType) {
switch msgType {
case core.Erro:
p.Diagnostics = append(p.Diagnostics, buildDiagnostic(err, DiagnosticSeverityError))
case core.Warn:
p.Diagnostics = append(p.Diagnostics, buildDiagnostic(err, DiagnosticSeverityWarning))
case core.Info:
p.Diagnostics = append(p.Diagnostics, buildDiagnostic(err, DiagnosticSeverityInformation))
case core.Succ:
return
default:
panic("unreached")
}
}

func buildDiagnostic(err *core.Error, severity DiagnosticSeverity) Diagnostic {
var tags []DiagnosticTag
if len(err.Types) > 0 {
for _, typ := range err.Types {
Expand All @@ -129,10 +147,16 @@ func BuildDiagnostic(err *core.Error, severity DiagnosticSeverity) Diagnostic {
}
}

msg := err.Error()
if err.Err != nil {
msg = err.Err.Error()
}

d := Diagnostic{
Range: err.Location.Range,
Message: err.Err.Error(),
Message: msg,
Severity: severity,
Source: core.Name,
}
if len(tags) > 0 {
d.Tags = tags
Expand Down
26 changes: 24 additions & 2 deletions internal/lsp/protocol/diagnostic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,41 @@ import (
"github.com/caixw/apidoc/v7/internal/locale"
)

func TestPublishDiagnosticParams_AppendDiagnostic(t *testing.T) {
a := assert.New(t)
p := NewPublishDiagnosticsParams(core.URI("test.go"))
a.NotNil(p).
Equal(p.URI, core.URI("test.go")).
Empty(p.Diagnostics)

p.AppendDiagnostic(core.NewError(locale.ErrInvalidUTF8Character), core.Erro)
a.Equal(1, len(p.Diagnostics))
p.AppendDiagnostic(core.NewError(locale.ErrInvalidUTF8Character), core.Warn)
a.Equal(2, len(p.Diagnostics))
p.AppendDiagnostic(core.NewError(locale.ErrInvalidUTF8Character), core.Info)
a.Equal(3, len(p.Diagnostics))
// 忽略 core.Succ
p.AppendDiagnostic(core.NewError(locale.ErrInvalidUTF8Character), core.Succ)
a.Equal(3, len(p.Diagnostics))

a.Panic(func() {
p.AppendDiagnostic(core.NewError(locale.ErrInvalidUTF8Character), 100)
})
}

func TestBuildDiagnostic(t *testing.T) {
a := assert.New(t)

err := core.NewError(locale.ErrInvalidUTF8Character).WithLocation(core.Location{
Range: core.Range{Start: core.Position{Line: 1}},
})
d := BuildDiagnostic(err, DiagnosticSeverityWarning)
d := buildDiagnostic(err, DiagnosticSeverityWarning)
a.Equal(d.Severity, DiagnosticSeverityWarning)
a.Empty(d.Tags)
a.Equal(d.Range.Start.Line, 1)

err = err.AddTypes(core.ErrorTypeDeprecated, core.ErrorTypeUnused)
d = BuildDiagnostic(err, DiagnosticSeverityError)
d = buildDiagnostic(err, DiagnosticSeverityError)
a.Equal(d.Severity, DiagnosticSeverityError)
a.Equal(d.Tags, []DiagnosticTag{DiagnosticTagDeprecated, DiagnosticTagUnnecessary})
a.Equal(d.Range.Start.Line, 1)
Expand Down
2 changes: 1 addition & 1 deletion internal/lsp/protocol/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Hover struct {

// An optional range is a range inside a text document
// that is used to visualize a hover, e.g. by changing the background color.
Range core.Range `json:"range,omitempty"`
Range core.Range `json:"range"`
}

// MarshalJSON 允许在 hover 为空值是返回 null
Expand Down
48 changes: 48 additions & 0 deletions internal/lsp/protocol/hover_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT

package protocol

import (
"encoding/json"
"testing"

"github.com/issue9/assert"

"github.com/caixw/apidoc/v7/core"
)

func TestHover_MarshalJSON(t *testing.T) {
a := assert.New(t)

h := &Hover{}
data, err := json.Marshal(h)
a.NotError(err).Equal(string(data), `null`)

h.Range = core.Range{
Start: core.Position{Line: 0, Character: 10},
End: core.Position{Line: 1, Character: 10},
}
data, err = json.Marshal(h)
a.NotError(err).Equal(string(data), `null`)

h.Contents = MarkupContent{
Kind: MarkupKindPlainText,
}
data, err = json.MarshalIndent(h, "", "\t")
a.NotError(err).Equal(string(data), `{
"contents": {
"kind": "plaintext",
"value": ""
},
"range": {
"start": {
"line": 0,
"character": 10
},
"end": {
"line": 1,
"character": 10
}
}
}`)
}
Loading

0 comments on commit dfe1133

Please sign in to comment.