Skip to content

Commit

Permalink
Language server: Publish Diagnostics.
Browse files Browse the repository at this point in the history
Diagnostics found in the parsed file (syntax
or linting) are published as a Notification back
to the client.

Introduce a verible-lsp-adapter set of utility
functions (right now: exactly one), that simply converts
the internal verible state of syntax error and lint status
in Language Server compatible objects - converting
one struct into another.
Since these are the code-generated structs, these then
can be automatically serialized to json.

The verible-verilog-ls_test.sh walks through all the
functionality and looking at the returned diagnostics.
In particular we test what happens if the document is
edited to fix an issue - we get a diagnostic back that
is empty. This test walks all code-paths so gives us
100% coverage.

Signed-off-by: Henner Zeller <[email protected]>
  • Loading branch information
hzeller committed Oct 19, 2021
1 parent a42d7af commit 33ccb94
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 0 deletions.
11 changes: 11 additions & 0 deletions common/lsp/lsp-protocol.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,14 @@ DidSaveTextDocumentParams: # textDocument/didSave

DidCloseTextDocumentParams: # textDocument/didClose
textDocument: TextDocumentIdentifier

# -- textDocument/publishDiagnostics
Diagnostic:
range: Range
source: string = "verible"
message: string

# This is a notification we send when we found something.
PublishDiagnosticsParams:
uri: string
diagnostics+: Diagnostic
14 changes: 14 additions & 0 deletions verilog/tools/ls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,25 @@ cc_library(
]
)

cc_library(
name = "verible-lsp-adapter",
srcs = [ "verible-lsp-adapter.cc"],
hdrs = [ "verible-lsp-adapter.h" ],
deps = [
":lsp-parse-buffer",
"//common/lsp:lsp-protocol",
"//common/lsp:lsp-protocol-operators",
"//verilog/analysis:verilog_analyzer",
"//verilog/analysis:verilog_linter",
]
)

cc_binary(
name = "verible-verilog-ls",
srcs = ["verilog_ls.cc", "lsp-parse-buffer.h"],
deps = [
":lsp-parse-buffer",
":verible-lsp-adapter",
"//common/lsp:lsp-protocol",
"//common/lsp:json-rpc-dispatcher",
"//common/lsp:message-stream-splitter",
Expand Down
93 changes: 93 additions & 0 deletions verilog/tools/ls/verible-lsp-adapter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2021 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "verilog/tools/ls/verible-lsp-adapter.h"

#include "common/lsp/lsp-protocol-operators.h"
#include "common/lsp/lsp-protocol.h"
#include "nlohmann/json.hpp"
#include "verilog/analysis/verilog_analyzer.h"
#include "verilog/analysis/verilog_linter.h"
#include "verilog/tools/ls/lsp-parse-buffer.h"

namespace verilog {
// Convert our representation of a linter violation to a LSP-Diagnostic
static verible::lsp::Diagnostic ViolationToDiagnostic(
const verilog::LintViolationWithStatus &v, absl::string_view base,
const verible::LineColumnMap &lc_map) {
const verible::LintViolation &violation = *v.violation;
verible::LineColumn start = lc_map(violation.token.left(base));
verible::LineColumn end = lc_map(violation.token.right(base));
const char *fix_msg = violation.autofixes.empty() ? "" : " (fix available)";
return verible::lsp::Diagnostic{
.range =
{
.start = {.line = start.line, .character = start.column},
.end = {.line = end.line, .character = end.column},
},
.message = absl::StrCat(violation.reason, " ", v.status->url, "[",
v.status->lint_rule_name, "]", fix_msg),
};
}

std::vector<verible::lsp::Diagnostic> CreateDiagnostics(
const BufferTracker &tracker) {
// Diagnostics should come from the latest state, including all the
// syntax errors.
const ParsedBuffer *const current = tracker.current();
if (!current) return {};
// TODO: files that generate a lot of messages will create a huge
// output. So we limit the messages here.
// However, we should work towards emitting them around the last known
// edit point in the document as this is what the user sees.
static constexpr int kMaxMessages = 100;
const auto &rejected_tokens = current->parser().GetRejectedTokens();
auto const &lint_violations =
verilog::GetSortedViolations(current->lint_result());
std::vector<verible::lsp::Diagnostic> result;
int remaining = rejected_tokens.size() + lint_violations.size();
if (remaining > kMaxMessages) remaining = kMaxMessages;
result.reserve(remaining);
for (const auto &rejected_token : rejected_tokens) {
current->parser().ExtractLinterTokenErrorDetail(
rejected_token,
[&result](const std::string &filename, verible::LineColumnRange range,
verible::AnalysisPhase phase, absl::string_view token_text,
absl::string_view context_line, const std::string &msg) {
// Note: msg is currently empty and not useful.
const auto message = (phase == verible::AnalysisPhase::kLexPhase)
? "token error"
: "syntax error";
result.emplace_back(verible::lsp::Diagnostic{
.range{.start{.line = range.start.line,
.character = range.start.column},
.end{.line = range.end.line, //
.character = range.end.column}},
.message = message,
});
});
if (--remaining <= 0) break;
}

const absl::string_view base = current->parser().Data().Contents();
verible::LineColumnMap line_column_map(base);
for (const auto &v : lint_violations) {
result.emplace_back(ViolationToDiagnostic(v, base, line_column_map));
if (--remaining <= 0) break;
}
return result;
}

} // namespace verilog
34 changes: 34 additions & 0 deletions verilog/tools/ls/verible-lsp-adapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2021 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef VERILOG_TOOLS_LS_VERIBLE_LSP_ADAPTER_H
#define VERILOG_TOOLS_LS_VERIBLE_LSP_ADAPTER_H

#include <vector>

#include "common/lsp/lsp-protocol.h"
#include "nlohmann/json.hpp"
#include "verilog/tools/ls/lsp-parse-buffer.h"

// Adapter functions converting verible state into lsp objects.

namespace verilog {

// Given the output of the parser and a lint status, create a diagnostic
// output to be sent in textDocument/publishDiagnostics notification.
std::vector<verible::lsp::Diagnostic> CreateDiagnostics(const BufferTracker &);

} // namespace verilog
#endif // VERILOG_TOOLS_LS_VERIBLE_LSP_ADAPTER_H
33 changes: 33 additions & 0 deletions verilog/tools/ls/verible-verilog-ls_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ MSG_OUT=${TEST_TMPDIR:-/tmp/}/test-lsp-out-msg.txt
# Starting up server, sending two files, a file with a parse error and
# a file that parses, but has a EOF newline linting diagnostic.
#
# We get the diagnostic messages for both of these files, one reporting
# a syntax error, one reporting a lint error.
#
# We then modify the second file and edit the needed newline at the end of
# the buffer, and then get an update with zero diagnostic errors back.
#
# TODO: maybe this awk-script should be replaced with something that allows
# multi-line input with comment.
awk '{printf("Content-Length: %d\r\n\r\n%s", length($0), $0)}' > ${TMP_IN} <<EOF
Expand All @@ -52,6 +58,33 @@ cat > "${JSON_EXPECTED}" <<EOF
}
}
},
{
"json_contains": {
"method":"textDocument/publishDiagnostics",
"params": {
"uri": "file://syntaxerror.sv",
"diagnostics":[{"message":"syntax error"}]
}
}
},
{
"json_contains": {
"method":"textDocument/publishDiagnostics",
"params": {
"uri": "file://mini.sv",
"diagnostics":[{"message":"File must end with a newline. [Style: posix-file-endings][posix-eof] (fix available)"}]
}
}
},
{
"json_contains": {
"method":"textDocument/publishDiagnostics",
"params": {
"uri": "file://mini.sv",
"diagnostics":[]
}
}
},
{
"json_contains": { "id":100 }
}
Expand Down
22 changes: 22 additions & 0 deletions verilog/tools/ls/verilog_ls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "common/lsp/message-stream-splitter.h"
#include "common/util/init_command_line.h"
#include "verilog/tools/ls/lsp-parse-buffer.h"
#include "verilog/tools/ls/verible-lsp-adapter.h"

// Windows specific implementation of read()
#ifndef _WIN32
Expand Down Expand Up @@ -67,6 +68,19 @@ static InitializeResult InitializeServer(const nlohmann::json &params) {
return result;
}

// Publish a diagnostic sent to the server.
static void SendDiagnostics(const std::string &uri,
const verilog::BufferTracker &buffer_tracker,
JsonRpcDispatcher *dispatcher) {
// TODO(hzeller): Cache result and rate-limit.
// This should not send anything if the diagnostics we're about to
// send would be exactly the same as last time.
verible::lsp::PublishDiagnosticsParams params;
params.uri = uri;
params.diagnostics = verilog::CreateDiagnostics(buffer_tracker);
dispatcher->SendNotification("textDocument/publishDiagnostics", params);
}

int main(int argc, char *argv[]) {
const auto file_args = verible::InitCommandLine(argv[0], &argc, &argv);

Expand Down Expand Up @@ -106,6 +120,14 @@ int main(int argc, char *argv[]) {
// Subscribe the parsed buffers to changes updating the text edit buffers
buffers.SetChangeListener(parsed_buffers.GetSubscriptionCallback());

// Whenever there is a new parse result ready, use that as an opportunity
// to send diagnostics to the client.
parsed_buffers.SetChangeListener(
[&dispatcher](const std::string &uri,
const verilog::BufferTracker &buffer_tracker) {
SendDiagnostics(uri, buffer_tracker, &dispatcher);
});

// -- Register JSON RPC callbacks

// Exchange of capabilities.
Expand Down

0 comments on commit 33ccb94

Please sign in to comment.