From f89985d0f4c24d8e0a523e1ce8bab208371e5c92 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Fri, 7 Feb 2025 15:04:35 -0800 Subject: [PATCH] Add support for publishDiagnostics (#4912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2025-02-06 at 3 02 18 PM Requires the caching work in #4897 --- toolchain/diagnostics/coverage_test.cpp | 9 ++ toolchain/diagnostics/diagnostic.h | 3 + toolchain/diagnostics/diagnostic_consumer.cpp | 2 +- toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/diagnostics/mocks.cpp | 4 +- toolchain/diagnostics/mocks.h | 2 +- toolchain/language_server/BUILD | 6 +- toolchain/language_server/context.cpp | 122 +++++++++++++++++- toolchain/language_server/context.h | 22 +++- .../handle_document_symbol.cpp | 1 - .../language_server/handle_text_document.cpp | 8 +- toolchain/language_server/language_server.cpp | 5 +- .../testdata/document_symbol/basics.carbon | 31 ++++- .../text_document/change_count.carbon | 13 +- .../testdata/text_document/diagnostics.carbon | 109 ++++++++++++++++ .../text_document/open_change_close.carbon | 24 +++- .../text_document/open_duplicate.carbon | 24 +++- 17 files changed, 349 insertions(+), 37 deletions(-) create mode 100644 toolchain/language_server/testdata/text_document/diagnostics.carbon diff --git a/toolchain/diagnostics/coverage_test.cpp b/toolchain/diagnostics/coverage_test.cpp index 37ce41d5c91e..3791e398e727 100644 --- a/toolchain/diagnostics/coverage_test.cpp +++ b/toolchain/diagnostics/coverage_test.cpp @@ -42,6 +42,15 @@ constexpr DiagnosticKind UntestedDiagnosticKinds[] = { // This is a little long but is tested in lex/numeric_literal_test.cpp. DiagnosticKind::TooManyDigits, + + // TODO: This can only fire if the first message in a diagnostic is rooted + // in a file other than the file being compiled. The language server + // currently only supports compiling one file at a time. Do one of: + // - When imports are supported, find a diagnostic whose first message isn't + // in the current file. + // - Require all diagnostics produced by compiling have their first location + // be in the file being compiled, never an import. + DiagnosticKind::LanguageServerDiagnosticInWrongFile, }; // Looks for diagnostic kinds that aren't covered by a file_test. diff --git a/toolchain/diagnostics/diagnostic.h b/toolchain/diagnostics/diagnostic.h index 95e51fb9d2d7..5bce1d963d3d 100644 --- a/toolchain/diagnostics/diagnostic.h +++ b/toolchain/diagnostics/diagnostic.h @@ -79,6 +79,9 @@ struct DiagnosticLoc { // A message composing a diagnostic. This may be the main message, but can also // be notes providing more information. struct DiagnosticMessage { + // Helper for calling `format_fn`. + auto Format() const -> std::string { return format_fn(*this); } + // The diagnostic's kind. DiagnosticKind kind; diff --git a/toolchain/diagnostics/diagnostic_consumer.cpp b/toolchain/diagnostics/diagnostic_consumer.cpp index 6d1a6de8d208..a5c928e28327 100644 --- a/toolchain/diagnostics/diagnostic_consumer.cpp +++ b/toolchain/diagnostics/diagnostic_consumer.cpp @@ -31,7 +31,7 @@ auto StreamDiagnosticConsumer::HandleDiagnostic(Diagnostic diagnostic) -> void { case DiagnosticLevel::LocationInfo: break; } - *stream_ << message.format_fn(message); + *stream_ << message.Format(); if (include_diagnostic_kind_) { *stream_ << " [" << message.kind << "]"; } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 02ea70cd01c3..d93e41732496 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -452,6 +452,7 @@ CARBON_DIAGNOSTIC_KIND(LanguageServerUnsupportedNotification) CARBON_DIAGNOSTIC_KIND(LanguageServerOpenDuplicateFile) CARBON_DIAGNOSTIC_KIND(LanguageServerUnsupportedChanges) CARBON_DIAGNOSTIC_KIND(LanguageServerCloseUnknownFile) +CARBON_DIAGNOSTIC_KIND(LanguageServerDiagnosticInWrongFile) // ============================================================================ // Other diagnostics diff --git a/toolchain/diagnostics/mocks.cpp b/toolchain/diagnostics/mocks.cpp index 3be80e6bd234..65af0af99056 100644 --- a/toolchain/diagnostics/mocks.cpp +++ b/toolchain/diagnostics/mocks.cpp @@ -11,8 +11,8 @@ void PrintTo(const Diagnostic& diagnostic, std::ostream* os) { PrintTo(diagnostic.level, os); for (const auto& message : diagnostic.messages) { *os << ", {" << message.loc.filename << ":" << message.loc.line_number - << ":" << message.loc.column_number << ", \"" - << message.format_fn(message) << "}"; + << ":" << message.loc.column_number << ", \"" << message.Format() + << "}"; } *os << "\"}"; } diff --git a/toolchain/diagnostics/mocks.h b/toolchain/diagnostics/mocks.h index d8a01fde2acd..0f42951583ef 100644 --- a/toolchain/diagnostics/mocks.h +++ b/toolchain/diagnostics/mocks.h @@ -19,7 +19,7 @@ class MockDiagnosticConsumer : public DiagnosticConsumer { // NOLINTNEXTLINE(modernize-use-trailing-return-type): From the macro. MATCHER_P(IsDiagnosticMessageString, matcher, "") { const DiagnosticMessage& message = arg; - return testing::ExplainMatchResult(matcher, message.format_fn(message), + return testing::ExplainMatchResult(matcher, message.Format(), result_listener); } diff --git a/toolchain/language_server/BUILD b/toolchain/language_server/BUILD index f95656421878..5ec142b5cf87 100644 --- a/toolchain/language_server/BUILD +++ b/toolchain/language_server/BUILD @@ -31,17 +31,20 @@ cc_library( srcs = ["context.cpp"], hdrs = ["context.h"], deps = [ + "//common:check", "//common:map", + "//common:raw_string_ostream", "//toolchain/base:shared_value_stores", + "//toolchain/check", "//toolchain/diagnostics:diagnostic_emitter", "//toolchain/diagnostics:file_diagnostics", - "//toolchain/diagnostics:null_diagnostics", "//toolchain/lex", "//toolchain/lex:tokenized_buffer", "//toolchain/parse", "//toolchain/parse:tree", "//toolchain/sem_ir:file", "//toolchain/source:source_buffer", + "@llvm-project//clang-tools-extra/clangd:ClangDaemon", ], ) @@ -52,7 +55,6 @@ cc_library( deps = [ ":context", "//toolchain/base:shared_value_stores", - "//toolchain/diagnostics:null_diagnostics", "//toolchain/lex", "//toolchain/parse", "//toolchain/parse:node_kind", diff --git a/toolchain/language_server/context.cpp b/toolchain/language_server/context.cpp index 5d0039658931..f1bcb8bfa664 100644 --- a/toolchain/language_server/context.cpp +++ b/toolchain/language_server/context.cpp @@ -6,8 +6,12 @@ #include +#include "common/check.h" +#include "common/raw_string_ostream.h" #include "toolchain/base/shared_value_stores.h" -#include "toolchain/diagnostics/null_diagnostics.h" +#include "toolchain/check/check.h" +#include "toolchain/diagnostics/diagnostic.h" +#include "toolchain/diagnostics/diagnostic_consumer.h" #include "toolchain/lex/lex.h" #include "toolchain/lex/tokenized_buffer.h" #include "toolchain/parse/parse.h" @@ -15,7 +19,86 @@ namespace Carbon::LanguageServer { -auto Context::File::SetText(Context& context, llvm::StringRef text) -> void { +// A consumer for turning diagnostics into a `textDocument/publishDiagnostics` +// notification. +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics +class PublishDiagnosticConsumer : public DiagnosticConsumer { + public: + // Initializes params with the target file information. + explicit PublishDiagnosticConsumer(Context* context, + const clang::clangd::URIForFile& uri, + std::optional version) + : context_(context), params_{.uri = uri, .version = version} {} + + // Turns a diagnostic into an LSP diagnostic. + auto HandleDiagnostic(Diagnostic diagnostic) -> void override { + const auto& message = diagnostic.messages[0]; + if (message.loc.filename != params_.uri.file()) { + // `pushDiagnostic` requires diagnostics to be associated with a location + // in the current file. Suppress diagnostics rooted in other files. + // TODO: Consider if there's a better way to handle this. + RawStringOstream stream; + StreamDiagnosticConsumer consumer(&stream); + consumer.HandleDiagnostic(diagnostic); + + CARBON_DIAGNOSTIC(LanguageServerDiagnosticInWrongFile, Warning, + "dropping diagnostic in {0}:\n{1}", std::string, + std::string); + context_->file_emitter().Emit( + params_.uri.file(), LanguageServerDiagnosticInWrongFile, + message.loc.filename.str(), stream.TakeStr()); + return; + } + + // Add the main message. + params_.diagnostics.push_back(clang::clangd::Diagnostic{ + .range = GetRange(message.loc), + .severity = GetSeverity(diagnostic.level), + .source = "carbon", + .message = message.Format(), + }); + // TODO: Figure out constructing URIs for note locations. + } + + // Returns the constructed request. + auto params() -> llvm::json::Value { return params_; } + + private: + // Returns the LSP range for a diagnostic. Note that Carbon uses 1-based + // numbers while LSP uses 0-based. + auto GetRange(const DiagnosticLoc& loc) -> clang::clangd::Range { + return {.start = {.line = loc.line_number - 1, + .character = loc.column_number - 1}, + .end = {.line = loc.line_number, + .character = loc.column_number + loc.length}}; + } + + // Converts a diagnostic level to an LSP severity. + auto GetSeverity(DiagnosticLevel level) -> int { + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticSeverity + enum class DiagnosticSeverity { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, + }; + + switch (level) { + case DiagnosticLevel::Error: + return static_cast(DiagnosticSeverity::Error); + case DiagnosticLevel::Warning: + return static_cast(DiagnosticSeverity::Warning); + default: + CARBON_FATAL("Unexpected diagnostic level: {0}", level); + } + } + + Context* context_; + clang::clangd::PublishDiagnosticsParams params_; +}; + +auto Context::File::SetText(Context& context, std::optional version, + llvm::StringRef text) -> void { // Clear state dependent on the source text. tree_and_subtrees_.reset(); tree_.reset(); @@ -23,28 +106,53 @@ auto Context::File::SetText(Context& context, llvm::StringRef text) -> void { value_stores_.reset(); source_.reset(); + // A consumer to gather diagnostics for the file. + PublishDiagnosticConsumer consumer(&context, uri_, version); + // TODO: Make the processing asynchronous, to better handle rapid text // updates. CARBON_CHECK(!source_ && !value_stores_ && !tokens_ && !tree_, "We currently cache everything together"); // TODO: Diagnostics should be passed to the LSP instead of dropped. - auto& null_consumer = NullDiagnosticConsumer(); std::optional source = - SourceBuffer::MakeFromStringCopy(filename_, text, null_consumer); + SourceBuffer::MakeFromStringCopy(uri_.file(), text, consumer); if (!source) { // Failing here should be rare, but provide stub data for recovery so that // we can have a simple API. - source = SourceBuffer::MakeFromStringCopy(filename_, "", null_consumer); + source = SourceBuffer::MakeFromStringCopy(uri_.file(), "", consumer); CARBON_CHECK(source, "Making an empty buffer should always succeed"); } source_ = std::make_unique(std::move(*source)); value_stores_ = std::make_unique(); tokens_ = std::make_unique( - Lex::Lex(*value_stores_, *source_, null_consumer)); + Lex::Lex(*value_stores_, *source_, consumer)); tree_ = std::make_unique( - Parse::Parse(*tokens_, null_consumer, context.vlog_stream())); + Parse::Parse(*tokens_, consumer, context.vlog_stream())); tree_and_subtrees_ = std::make_unique(*tokens_, *tree_); + + SemIR::File sem_ir(tree_.get(), SemIR::CheckIRId(0), tree_->packaging_decl(), + *value_stores_, uri_.file().str()); + auto getter = [this]() -> const Parse::TreeAndSubtrees& { + return *tree_and_subtrees_; + }; + // TODO: Support cross-file checking when multiple files have edits. + llvm::SmallVector units = {{.consumer = &consumer, + .value_stores = value_stores_.get(), + .timings = nullptr, + .tree_and_subtrees_getter = getter, + .sem_ir = &sem_ir}}; + llvm::IntrusiveRefCntPtr fs = + new llvm::vfs::InMemoryFileSystem; + // TODO: Include the prelude. + Check::CheckParseTrees(units, /*prelude_import=*/false, fs, + context.vlog_stream(), /*fuzzing=*/false); + + // Note we need to publish diagnostics even when empty. + // TODO: Consider caching previously published diagnostics and only publishing + // when they change. + context.outgoing().notify("textDocument/publishDiagnostics", + consumer.params()); } auto Context::LookupFile(llvm::StringRef filename) -> File* { diff --git a/toolchain/language_server/context.h b/toolchain/language_server/context.h index 90a8f844e212..8c4c8b1b784b 100644 --- a/toolchain/language_server/context.h +++ b/toolchain/language_server/context.h @@ -8,6 +8,7 @@ #include #include +#include "clang-tools-extra/clangd/LSPBinder.h" #include "common/map.h" #include "toolchain/base/shared_value_stores.h" #include "toolchain/diagnostics/diagnostic_consumer.h" @@ -26,10 +27,11 @@ class Context { // Cached information for an open file. class File { public: - explicit File(std::string filename) : filename_(std::move(filename)) {} + explicit File(clang::clangd::URIForFile uri) : uri_(std::move(uri)) {} // Changes the file's text, updating dependent state. - auto SetText(Context& context, llvm::StringRef text) -> void; + auto SetText(Context& context, std::optional version, + llvm::StringRef text) -> void; auto tree_and_subtrees() const -> const Parse::TreeAndSubtrees& { return *tree_and_subtrees_; @@ -37,7 +39,7 @@ class Context { private: // The filename, stable across instances. - std::string filename_; + clang::clangd::URIForFile uri_; // Current file content, and derived values. std::unique_ptr source_; @@ -47,19 +49,24 @@ class Context { std::unique_ptr tree_and_subtrees_; }; - // `consumer` and `emitter` are required. `vlog_stream` is optional. - explicit Context(llvm::raw_ostream* vlog_stream, DiagnosticConsumer* consumer) + // `vlog_stream` is optional; other parameters are required. + explicit Context(llvm::raw_ostream* vlog_stream, DiagnosticConsumer* consumer, + clang::clangd::LSPBinder::RawOutgoing* outgoing) : vlog_stream_(vlog_stream), file_emitter_(consumer), - no_loc_emitter_(consumer) {} + no_loc_emitter_(consumer), + outgoing_(outgoing) {} // Returns a reference to the file if it's known, or diagnoses and returns // null. auto LookupFile(llvm::StringRef filename) -> File*; + auto vlog_stream() -> llvm::raw_ostream* { return vlog_stream_; } auto file_emitter() -> FileDiagnosticEmitter& { return file_emitter_; } auto no_loc_emitter() -> NoLocDiagnosticEmitter& { return no_loc_emitter_; } - auto vlog_stream() -> llvm::raw_ostream* { return vlog_stream_; } + auto outgoing() -> clang::clangd::LSPBinder::RawOutgoing& { + return *outgoing_; + } auto files() -> Map& { return files_; } @@ -68,6 +75,7 @@ class Context { llvm::raw_ostream* vlog_stream_; FileDiagnosticEmitter file_emitter_; NoLocDiagnosticEmitter no_loc_emitter_; + clang::clangd::LSPBinder::RawOutgoing* outgoing_; // Content of files managed by the language client. Map files_; diff --git a/toolchain/language_server/handle_document_symbol.cpp b/toolchain/language_server/handle_document_symbol.cpp index 200653829dea..28262dfc9493 100644 --- a/toolchain/language_server/handle_document_symbol.cpp +++ b/toolchain/language_server/handle_document_symbol.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/base/shared_value_stores.h" -#include "toolchain/diagnostics/null_diagnostics.h" #include "toolchain/language_server/handle.h" #include "toolchain/lex/lex.h" #include "toolchain/parse/node_kind.h" diff --git a/toolchain/language_server/handle_text_document.cpp b/toolchain/language_server/handle_text_document.cpp index 350a178a055f..6be977d10e72 100644 --- a/toolchain/language_server/handle_text_document.cpp +++ b/toolchain/language_server/handle_text_document.cpp @@ -16,8 +16,9 @@ auto HandleDidOpenTextDocument( } auto insert_result = context.files().Insert( - filename, [&] { return Context::File(filename.str()); }); - insert_result.value().SetText(context, params.textDocument.text); + filename, [&] { return Context::File(params.textDocument.uri); }); + insert_result.value().SetText(context, params.textDocument.version, + params.textDocument.text); if (!insert_result.is_inserted()) { CARBON_DIAGNOSTIC(LanguageServerOpenDuplicateFile, Warning, "duplicate open file request; updating content"); @@ -43,7 +44,8 @@ auto HandleDidChangeTextDocument( return; } if (auto* file = context.LookupFile(filename)) { - file->SetText(context, params.contentChanges[0].text); + file->SetText(context, params.textDocument.version, + params.contentChanges[0].text); } } diff --git a/toolchain/language_server/language_server.cpp b/toolchain/language_server/language_server.cpp index 8eb4ad55d945..9ff7d28c8e64 100644 --- a/toolchain/language_server/language_server.cpp +++ b/toolchain/language_server/language_server.cpp @@ -55,10 +55,9 @@ auto Run(FILE* input_stream, llvm::raw_ostream& output_stream, clang::clangd::newJSONTransport(input_stream, output_stream, /*InMirror=*/nullptr, /*Pretty=*/true)); - Context context(vlog_stream, &consumer); - // TODO: Use error_stream in IncomingMessages to report dropped errors. - IncomingMessages incoming(transport.get(), &context); OutgoingMessages outgoing(transport.get()); + Context context(vlog_stream, &consumer, &outgoing); + IncomingMessages incoming(transport.get(), &context); // Run the server loop. llvm::Error err = transport->loop(incoming); diff --git a/toolchain/language_server/testdata/document_symbol/basics.carbon b/toolchain/language_server/testdata/document_symbol/basics.carbon index 5d41fe04d20c..3f566439f957 100644 --- a/toolchain/language_server/testdata/document_symbol/basics.carbon +++ b/toolchain/language_server/testdata/document_symbol/basics.carbon @@ -18,7 +18,7 @@ ]] [[@LSP-NOTIFY:textDocument/didOpen: "textDocument": {"uri": "file:/invalid.carbon", "languageId": "carbon", - "text": "text"} + "text": "// Empty"} ]] [[@LSP-CALL:textDocument/documentSymbol: "textDocument": {"uri": "file:/invalid.carbon"} @@ -34,18 +34,45 @@ // --- AUTOUPDATE-SPLIT -// CHECK:STDOUT: Content-Length: 49{{\r}} +// CHECK:STDOUT: Content-Length: 145{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///empty.carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: }Content-Length: 49{{\r}} // CHECK:STDOUT: {{\r}} // CHECK:STDOUT: { // CHECK:STDOUT: "id": 1, // CHECK:STDOUT: "jsonrpc": "2.0", // CHECK:STDOUT: "result": [] +// CHECK:STDOUT: }Content-Length: 147{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///invalid.carbon" +// CHECK:STDOUT: } // CHECK:STDOUT: }Content-Length: 49{{\r}} // CHECK:STDOUT: {{\r}} // CHECK:STDOUT: { // CHECK:STDOUT: "id": 2, // CHECK:STDOUT: "jsonrpc": "2.0", // CHECK:STDOUT: "result": [] +// CHECK:STDOUT: }Content-Length: 142{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///fn.carbon" +// CHECK:STDOUT: } // CHECK:STDOUT: }Content-Length: 459{{\r}} // CHECK:STDOUT: {{\r}} // CHECK:STDOUT: { diff --git a/toolchain/language_server/testdata/text_document/change_count.carbon b/toolchain/language_server/testdata/text_document/change_count.carbon index 4feae700916b..d1fbd895a56c 100644 --- a/toolchain/language_server/testdata/text_document/change_count.carbon +++ b/toolchain/language_server/testdata/text_document/change_count.carbon @@ -11,7 +11,7 @@ // --- STDIN [[@LSP-NOTIFY:textDocument/didOpen: "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", - "text": "test"} + "text": "// Empty"} ]] [[@LSP-NOTIFY:textDocument/didChange: "textDocument": {"uri": "file:/test.carbon"}, @@ -36,4 +36,13 @@ // CHECK:STDERR: // CHECK:STDERR: /test.carbon: warning: received unsupported contentChanges count: 2 [LanguageServerUnsupportedChanges] // CHECK:STDERR: -// CHECK:STDOUT: +// CHECK:STDOUT: Content-Length: 144{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///test.carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: } diff --git a/toolchain/language_server/testdata/text_document/diagnostics.carbon b/toolchain/language_server/testdata/text_document/diagnostics.carbon new file mode 100644 index 000000000000..96251d03b152 --- /dev/null +++ b/toolchain/language_server/testdata/text_document/diagnostics.carbon @@ -0,0 +1,109 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/text_document/diagnostics.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/text_document/diagnostics.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", + "version": 1, "text": "{"} +]] +[[@LSP-NOTIFY:textDocument/didChange: + "textDocument": {"uri": "file:/test.carbon", "version": 2}, + "contentChanges": [{"text": "fn F() { return (); }"}] +]] +[[@LSP-NOTIFY:textDocument/didClose: + "textDocument": {"uri": "file:/test.carbon"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDOUT: Content-Length: 1164{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [ +// CHECK:STDOUT: { +// CHECK:STDOUT: "message": "opening symbol without a corresponding closing symbol", +// CHECK:STDOUT: "range": { +// CHECK:STDOUT: "end": { +// CHECK:STDOUT: "character": 2, +// CHECK:STDOUT: "line": 1 +// CHECK:STDOUT: }, +// CHECK:STDOUT: "start": { +// CHECK:STDOUT: "character": 0, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: } +// CHECK:STDOUT: }, +// CHECK:STDOUT: "severity": 1, +// CHECK:STDOUT: "source": "carbon" +// CHECK:STDOUT: }, +// CHECK:STDOUT: { +// CHECK:STDOUT: "message": "unrecognized declaration introducer", +// CHECK:STDOUT: "range": { +// CHECK:STDOUT: "end": { +// CHECK:STDOUT: "character": 2, +// CHECK:STDOUT: "line": 1 +// CHECK:STDOUT: }, +// CHECK:STDOUT: "start": { +// CHECK:STDOUT: "character": 0, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: } +// CHECK:STDOUT: }, +// CHECK:STDOUT: "severity": 1, +// CHECK:STDOUT: "source": "carbon" +// CHECK:STDOUT: }, +// CHECK:STDOUT: { +// CHECK:STDOUT: "message": "semantics TODO: `handle invalid parse trees in `check``", +// CHECK:STDOUT: "range": { +// CHECK:STDOUT: "end": { +// CHECK:STDOUT: "character": 2, +// CHECK:STDOUT: "line": 1 +// CHECK:STDOUT: }, +// CHECK:STDOUT: "start": { +// CHECK:STDOUT: "character": 0, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: } +// CHECK:STDOUT: }, +// CHECK:STDOUT: "severity": 1, +// CHECK:STDOUT: "source": "carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: ], +// CHECK:STDOUT: "uri": "file:///test.carbon", +// CHECK:STDOUT: "version": 1 +// CHECK:STDOUT: } +// CHECK:STDOUT: }Content-Length: 507{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [ +// CHECK:STDOUT: { +// CHECK:STDOUT: "message": "no return expression should be provided in this context", +// CHECK:STDOUT: "range": { +// CHECK:STDOUT: "end": { +// CHECK:STDOUT: "character": 20, +// CHECK:STDOUT: "line": 1 +// CHECK:STDOUT: }, +// CHECK:STDOUT: "start": { +// CHECK:STDOUT: "character": 9, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: } +// CHECK:STDOUT: }, +// CHECK:STDOUT: "severity": 1, +// CHECK:STDOUT: "source": "carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: ], +// CHECK:STDOUT: "uri": "file:///test.carbon", +// CHECK:STDOUT: "version": 2 +// CHECK:STDOUT: } +// CHECK:STDOUT: } diff --git a/toolchain/language_server/testdata/text_document/open_change_close.carbon b/toolchain/language_server/testdata/text_document/open_change_close.carbon index 76d24c86b18a..cf8b55ef1acb 100644 --- a/toolchain/language_server/testdata/text_document/open_change_close.carbon +++ b/toolchain/language_server/testdata/text_document/open_change_close.carbon @@ -11,11 +11,11 @@ // --- STDIN [[@LSP-NOTIFY:textDocument/didOpen: "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", - "text": "test"} + "text": "// Empty"} ]] [[@LSP-NOTIFY:textDocument/didChange: "textDocument": {"uri": "file:/test.carbon"}, - "contentChanges": [{"text": "new content"}] + "contentChanges": [{"text": "// Differently empty"}] ]] [[@LSP-NOTIFY:textDocument/didClose: "textDocument": {"uri": "file:/test.carbon"} @@ -24,4 +24,22 @@ // --- AUTOUPDATE-SPLIT -// CHECK:STDOUT: +// CHECK:STDOUT: Content-Length: 144{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///test.carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: }Content-Length: 144{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///test.carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: } diff --git a/toolchain/language_server/testdata/text_document/open_duplicate.carbon b/toolchain/language_server/testdata/text_document/open_duplicate.carbon index ef0439a3c656..90817231162f 100644 --- a/toolchain/language_server/testdata/text_document/open_duplicate.carbon +++ b/toolchain/language_server/testdata/text_document/open_duplicate.carbon @@ -11,11 +11,11 @@ // --- STDIN [[@LSP-NOTIFY:textDocument/didOpen: "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", - "text": "test"} + "text": "// Empty"} ]] [[@LSP-NOTIFY:textDocument/didOpen: "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", - "text": "test"} + "text": "// Empty"} ]] [[@LSP-NOTIFY:exit]] @@ -23,4 +23,22 @@ // CHECK:STDERR: /test.carbon: warning: duplicate open file request; updating content [LanguageServerOpenDuplicateFile] // CHECK:STDERR: -// CHECK:STDOUT: +// CHECK:STDOUT: Content-Length: 144{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///test.carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: }Content-Length: 144{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "method": "textDocument/publishDiagnostics", +// CHECK:STDOUT: "params": { +// CHECK:STDOUT: "diagnostics": [], +// CHECK:STDOUT: "uri": "file:///test.carbon" +// CHECK:STDOUT: } +// CHECK:STDOUT: }