From b3911cdfc89f3b560fc00048d6f99280268fa63c Mon Sep 17 00:00:00 2001 From: River Riddle Date: Wed, 12 May 2021 13:01:59 -0700 Subject: [PATCH] [mlir-lsp-server] Add support for sending diagnostics to the client This allows for diagnostics emitted during parsing/verification to be surfaced to the user by the language client, as opposed to just being emitted to the logs like they are now. Differential Revision: https://reviews.llvm.org/D102293 --- mlir/include/mlir/Parser/AsmParserState.h | 1 + mlir/lib/Parser/AsmParserState.cpp | 4 + mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp | 28 ++- mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp | 102 ++++++++-- mlir/lib/Tools/mlir-lsp-server/MLIRServer.h | 7 +- .../Tools/mlir-lsp-server/lsp/Protocol.cpp | 41 ++++ mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.h | 77 +++++++ .../Tools/mlir-lsp-server/lsp/Transport.cpp | 24 +++ .../lib/Tools/mlir-lsp-server/lsp/Transport.h | 189 ++++++++---------- mlir/test/mlir-lsp-server/diagnostics.test | 35 ++++ 10 files changed, 389 insertions(+), 119 deletions(-) create mode 100644 mlir/test/mlir-lsp-server/diagnostics.test diff --git a/mlir/include/mlir/Parser/AsmParserState.h b/mlir/include/mlir/Parser/AsmParserState.h index b468f582f496..0b9e2553a805 100644 --- a/mlir/include/mlir/Parser/AsmParserState.h +++ b/mlir/include/mlir/Parser/AsmParserState.h @@ -82,6 +82,7 @@ public: AsmParserState(); ~AsmParserState(); + AsmParserState &operator=(AsmParserState &&other); //===--------------------------------------------------------------------===// // Access State diff --git a/mlir/lib/Parser/AsmParserState.cpp b/mlir/lib/Parser/AsmParserState.cpp index 1c629351b73e..8655cd1f963d 100644 --- a/mlir/lib/Parser/AsmParserState.cpp +++ b/mlir/lib/Parser/AsmParserState.cpp @@ -52,6 +52,10 @@ struct AsmParserState::Impl { AsmParserState::AsmParserState() : impl(std::make_unique()) {} AsmParserState::~AsmParserState() {} +AsmParserState &AsmParserState::operator=(AsmParserState &&other) { + impl = std::move(other.impl); + return *this; +} //===----------------------------------------------------------------------===// // Access State diff --git a/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp b/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp index 44d6f3da99f1..954482f48b5e 100644 --- a/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp @@ -59,6 +59,10 @@ struct LSPServer::Impl { MLIRServer &server; JSONTransport &transport; + /// An outgoing notification used to send diagnostics to the client when they + /// are ready to be processed. + OutgoingNotification publishDiagnostics; + /// Used to indicate that the 'shutdown' request was received from the /// Language Server client. bool shutdownRequestReceived = false; @@ -99,11 +103,21 @@ void LSPServer::Impl::onShutdown(const NoParams &, void LSPServer::Impl::onDocumentDidOpen( const DidOpenTextDocumentParams ¶ms) { - server.addOrUpdateDocument(params.textDocument.uri, params.textDocument.text); + PublishDiagnosticsParams diagParams(params.textDocument.uri); + server.addOrUpdateDocument(params.textDocument.uri, params.textDocument.text, + diagParams.diagnostics); + + // Publish any recorded diagnostics. + publishDiagnostics(diagParams); } void LSPServer::Impl::onDocumentDidClose( const DidCloseTextDocumentParams ¶ms) { server.removeDocument(params.textDocument.uri); + + // Empty out the diagnostics shown for this document. This will clear out + // anything currently displayed by the client for this document (e.g. in the + // "Problems" pane of VSCode). + publishDiagnostics(PublishDiagnosticsParams(params.textDocument.uri)); } void LSPServer::Impl::onDocumentDidChange( const DidChangeTextDocumentParams ¶ms) { @@ -111,8 +125,13 @@ void LSPServer::Impl::onDocumentDidChange( // to avoid this. if (params.contentChanges.size() != 1) return; + PublishDiagnosticsParams diagParams(params.textDocument.uri); server.addOrUpdateDocument(params.textDocument.uri, - params.contentChanges.front().text); + params.contentChanges.front().text, + diagParams.diagnostics); + + // Publish any recorded diagnostics. + publishDiagnostics(diagParams); } //===----------------------------------------------------------------------===// @@ -173,6 +192,11 @@ LogicalResult LSPServer::run() { // Hover messageHandler.method("textDocument/hover", impl.get(), &Impl::onHover); + // Diagnostics + impl->publishDiagnostics = + messageHandler.outgoingNotification( + "textDocument/publishDiagnostics"); + // Run the main loop of the transport. LogicalResult result = success(); if (llvm::Error error = impl->transport.run(messageHandler)) { diff --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp index 807e1864ed3b..f9b0bfb52635 100644 --- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp @@ -60,7 +60,28 @@ static Optional getLocationFromLoc(FileLineColLoc loc) { lsp::Position position; position.line = loc.getLine() - 1; position.character = loc.getColumn(); - return lsp::Location{*sourceURI, lsp::Range{position, position}}; + return lsp::Location{*sourceURI, lsp::Range(position)}; +} + +/// Returns a language server location from the given MLIR location, or None if +/// one couldn't be created. `uri` is an optional additional filter that, when +/// present, is used to filter sub locations that do not share the same uri. +static Optional +getLocationFromLoc(Location loc, const lsp::URIForFile *uri = nullptr) { + Optional location; + loc->walk([&](Location nestedLoc) { + FileLineColLoc fileLoc = nestedLoc.dyn_cast(); + if (!fileLoc) + return WalkResult::advance(); + + Optional sourceLoc = getLocationFromLoc(fileLoc); + if (sourceLoc && (!uri || sourceLoc->uri == *uri)) { + location = *sourceLoc; + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }); + return location; } /// Collect all of the locations from the given MLIR location that are not @@ -173,6 +194,56 @@ static void printDefBlockName(raw_ostream &os, printDefBlockName(os, def.block, def.definition.loc); } +/// Convert the given MLIR diagnostic to the LSP form. +static lsp::Diagnostic getLspDiagnoticFromDiag(Diagnostic &diag, + const lsp::URIForFile &uri) { + lsp::Diagnostic lspDiag; + lspDiag.source = "mlir"; + + // Note: Right now all of the diagnostics are treated as parser issues, but + // some are parser and some are verifier. + lspDiag.category = "Parse Error"; + + // Try to grab a file location for this diagnostic. + // TODO: For simplicity, we just grab the first one. It may be likely that we + // will need a more interesting heuristic here.' + Optional lspLocation = + getLocationFromLoc(diag.getLocation(), &uri); + if (lspLocation) + lspDiag.range = lspLocation->range; + + // Convert the severity for the diagnostic. + switch (diag.getSeverity()) { + case DiagnosticSeverity::Note: + llvm_unreachable("expected notes to be handled separately"); + case DiagnosticSeverity::Warning: + lspDiag.severity = lsp::DiagnosticSeverity::Warning; + break; + case DiagnosticSeverity::Error: + lspDiag.severity = lsp::DiagnosticSeverity::Error; + break; + case DiagnosticSeverity::Remark: + lspDiag.severity = lsp::DiagnosticSeverity::Information; + break; + } + lspDiag.message = diag.str(); + + // Attach any notes to the main diagnostic as related information. + std::vector relatedDiags; + for (Diagnostic ¬e : diag.getNotes()) { + lsp::Location noteLoc; + if (Optional loc = getLocationFromLoc(note.getLocation())) + noteLoc = *loc; + else + noteLoc.uri = uri; + relatedDiags.emplace_back(noteLoc, note.str()); + } + if (!relatedDiags.empty()) + lspDiag.relatedInformation = std::move(relatedDiags); + + return lspDiag; +} + //===----------------------------------------------------------------------===// // MLIRDocument //===----------------------------------------------------------------------===// @@ -182,7 +253,8 @@ namespace { /// document. struct MLIRDocument { MLIRDocument(const lsp::URIForFile &uri, StringRef contents, - DialectRegistry ®istry); + DialectRegistry ®istry, + std::vector &diagnostics); //===--------------------------------------------------------------------===// // Definitions and References @@ -227,15 +299,12 @@ struct MLIRDocument { } // namespace MLIRDocument::MLIRDocument(const lsp::URIForFile &uri, StringRef contents, - DialectRegistry ®istry) + DialectRegistry ®istry, + std::vector &diagnostics) : context(registry) { context.allowUnregisteredDialects(); ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) { - // TODO: What should we do with these diagnostics? - // * Cache and show to the user? - // * Ignore? - lsp::Logger::error("Error when parsing MLIR document `{0}`: `{1}`", - uri.file(), diag.str()); + diagnostics.push_back(getLspDiagnoticFromDiag(diag, uri)); }); // Try to parsed the given IR string. @@ -246,9 +315,13 @@ MLIRDocument::MLIRDocument(const lsp::URIForFile &uri, StringRef contents, } sourceMgr.AddNewSourceBuffer(std::move(memBuffer), llvm::SMLoc()); - if (failed( - parseSourceFile(sourceMgr, &parsedIR, &context, nullptr, &asmState))) + if (failed(parseSourceFile(sourceMgr, &parsedIR, &context, nullptr, + &asmState))) { + // If parsing failed, clear out any of the current state. + parsedIR.clear(); + asmState = AsmParserState(); return; + } } //===----------------------------------------------------------------------===// @@ -495,10 +568,11 @@ lsp::MLIRServer::MLIRServer(DialectRegistry ®istry) : impl(std::make_unique(registry)) {} lsp::MLIRServer::~MLIRServer() {} -void lsp::MLIRServer::addOrUpdateDocument(const URIForFile &uri, - StringRef contents) { - impl->documents[uri.file()] = - std::make_unique(uri, contents, impl->registry); +void lsp::MLIRServer::addOrUpdateDocument( + const URIForFile &uri, StringRef contents, + std::vector &diagnostics) { + impl->documents[uri.file()] = std::make_unique( + uri, contents, impl->registry, diagnostics); } void lsp::MLIRServer::removeDocument(const URIForFile &uri) { diff --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h index 6b974a5c9588..f3af2a67453d 100644 --- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h +++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.h @@ -16,6 +16,7 @@ namespace mlir { class DialectRegistry; namespace lsp { +struct Diagnostic; struct Hover; struct Location; struct Position; @@ -30,8 +31,10 @@ public: MLIRServer(DialectRegistry ®istry); ~MLIRServer(); - /// Add or update the document at the given URI. - void addOrUpdateDocument(const URIForFile &uri, StringRef contents); + /// Add or update the document at the given URI. Any diagnostics emitted for + /// this document should be added to `diagnostics` + void addOrUpdateDocument(const URIForFile &uri, StringRef contents, + std::vector &diagnostics); /// Remove the document with the given uri. void removeDocument(const URIForFile &uri); diff --git a/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.cpp b/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.cpp index 9b906243e0fa..8d4fbe180791 100644 --- a/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.cpp @@ -473,3 +473,44 @@ llvm::json::Value mlir::lsp::toJSON(const Hover &hover) { result["range"] = toJSON(*hover.range); return std::move(result); } + +//===----------------------------------------------------------------------===// +// DiagnosticRelatedInformation +//===----------------------------------------------------------------------===// + +llvm::json::Value mlir::lsp::toJSON(const DiagnosticRelatedInformation &info) { + return llvm::json::Object{ + {"location", info.location}, + {"message", info.message}, + }; +} + +//===----------------------------------------------------------------------===// +// Diagnostic +//===----------------------------------------------------------------------===// + +llvm::json::Value mlir::lsp::toJSON(const Diagnostic &diag) { + llvm::json::Object result{ + {"range", diag.range}, + {"severity", (int)diag.severity}, + {"message", diag.message}, + }; + if (diag.category) + result["category"] = *diag.category; + if (!diag.source.empty()) + result["source"] = diag.source; + if (diag.relatedInformation) + result["relatedInformation"] = *diag.relatedInformation; + return std::move(result); +} + +//===----------------------------------------------------------------------===// +// PublishDiagnosticsParams +//===----------------------------------------------------------------------===// + +llvm::json::Value mlir::lsp::toJSON(const PublishDiagnosticsParams ¶ms) { + return llvm::json::Object{ + {"uri", params.uri}, + {"diagnostics", params.diagnostics}, + }; +} diff --git a/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.h b/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.h index 78d313dfa951..de08fa247bd8 100644 --- a/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.h +++ b/mlir/lib/Tools/mlir-lsp-server/lsp/Protocol.h @@ -228,6 +228,10 @@ raw_ostream &operator<<(raw_ostream &os, const Position &value); //===----------------------------------------------------------------------===// struct Range { + Range() = default; + Range(Position start, Position end) : start(start), end(end) {} + Range(Position loc) : Range(loc, loc) {} + /// The range's start position. Position start; @@ -393,6 +397,79 @@ struct Hover { }; llvm::json::Value toJSON(const Hover &hover); +//===----------------------------------------------------------------------===// +// DiagnosticRelatedInformation +//===----------------------------------------------------------------------===// + +/// Represents a related message and source code location for a diagnostic. +/// This should be used to point to code locations that cause or related to a +/// diagnostics, e.g. when duplicating a symbol in a scope. +struct DiagnosticRelatedInformation { + DiagnosticRelatedInformation(Location location, std::string message) + : location(location), message(std::move(message)) {} + + /// The location of this related diagnostic information. + Location location; + /// The message of this related diagnostic information. + std::string message; +}; +llvm::json::Value toJSON(const DiagnosticRelatedInformation &info); + +//===----------------------------------------------------------------------===// +// Diagnostic +//===----------------------------------------------------------------------===// + +enum class DiagnosticSeverity { + /// It is up to the client to interpret diagnostics as error, warning, info or + /// hint. + Undetermined = 0, + Error = 1, + Warning = 2, + Information = 3, + Hint = 4 +}; + +struct Diagnostic { + /// The source range where the message applies. + Range range; + + /// The diagnostic's severity. Can be omitted. If omitted it is up to the + /// client to interpret diagnostics as error, warning, info or hint. + DiagnosticSeverity severity = DiagnosticSeverity::Undetermined; + + /// A human-readable string describing the source of this diagnostic, e.g. + /// 'typescript' or 'super lint'. + std::string source; + + /// The diagnostic's message. + std::string message; + + /// An array of related diagnostic information, e.g. when symbol-names within + /// a scope collide all definitions can be marked via this property. + Optional> relatedInformation; + + /// The diagnostic's category. Can be omitted. + /// An LSP extension that's used to send the name of the category over to the + /// client. The category typically describes the compilation stage during + /// which the issue was produced, e.g. "Semantic Issue" or "Parse Issue". + Optional category; +}; +llvm::json::Value toJSON(const Diagnostic &diag); + +//===----------------------------------------------------------------------===// +// PublishDiagnosticsParams +//===----------------------------------------------------------------------===// + +struct PublishDiagnosticsParams { + PublishDiagnosticsParams(URIForFile uri) : uri(uri) {} + + /// The URI for which diagnostic information is reported. + URIForFile uri; + /// The list of reported diagnostics. + std::vector diagnostics; +}; +llvm::json::Value toJSON(const PublishDiagnosticsParams ¶ms); + } // namespace lsp } // namespace mlir diff --git a/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.cpp b/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.cpp index c13fc85bb1d7..9468d39f273b 100644 --- a/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.cpp @@ -21,6 +21,30 @@ using namespace mlir::lsp; // Reply //===----------------------------------------------------------------------===// +namespace { +/// Function object to reply to an LSP call. +/// Each instance must be called exactly once, otherwise: +/// - if there was no reply, an error reply is sent +/// - if there were multiple replies, only the first is sent +class Reply { +public: + Reply(const llvm::json::Value &id, StringRef method, + JSONTransport &transport); + Reply(Reply &&other); + Reply &operator=(Reply &&) = delete; + Reply(const Reply &) = delete; + Reply &operator=(const Reply &) = delete; + + void operator()(llvm::Expected reply); + +private: + StringRef method; + std::atomic replied = {false}; + llvm::json::Value id; + JSONTransport *transport; +}; +} // namespace + Reply::Reply(const llvm::json::Value &id, llvm::StringRef method, JSONTransport &transport) : id(id), transport(&transport) {} diff --git a/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.h b/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.h index 2cbf8f1ddb23..23b565c7dce5 100644 --- a/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.h +++ b/mlir/lib/Tools/mlir-lsp-server/lsp/Transport.h @@ -28,107 +28,7 @@ namespace mlir { namespace lsp { -class JSONTransport; - -//===----------------------------------------------------------------------===// -// Reply -//===----------------------------------------------------------------------===// - -/// Function object to reply to an LSP call. -/// Each instance must be called exactly once, otherwise: -/// - if there was no reply, an error reply is sent -/// - if there were multiple replies, only the first is sent -class Reply { -public: - Reply(const llvm::json::Value &id, StringRef method, - JSONTransport &transport); - Reply(Reply &&other); - Reply &operator=(Reply &&) = delete; - Reply(const Reply &) = delete; - Reply &operator=(const Reply &) = delete; - - void operator()(llvm::Expected reply); - -private: - StringRef method; - std::atomic replied = {false}; - llvm::json::Value id; - JSONTransport *transport; -}; - -//===----------------------------------------------------------------------===// -// MessageHandler -//===----------------------------------------------------------------------===// - -/// A Callback is a void function that accepts Expected. This is -/// accepted by functions that logically return T. -template -using Callback = llvm::unique_function)>; - -/// A handler used to process the incoming transport messages. -class MessageHandler { -public: - MessageHandler(JSONTransport &transport) : transport(transport) {} - - bool onNotify(StringRef method, llvm::json::Value value); - bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id); - bool onReply(llvm::json::Value id, llvm::Expected result); - - template - static llvm::Expected parse(const llvm::json::Value &raw, - StringRef payloadName, StringRef payloadKind) { - T result; - llvm::json::Path::Root root; - if (fromJSON(raw, result, root)) - return std::move(result); - - // Dump the relevant parts of the broken message. - std::string context; - llvm::raw_string_ostream os(context); - root.printErrorContext(raw, os); - - // Report the error (e.g. to the client). - return llvm::make_error( - llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind, - fmt_consume(root.getError())), - ErrorCode::InvalidParams); - } - - template - void method(llvm::StringLiteral method, ThisT *thisPtr, - void (ThisT::*handler)(const Param &, Callback)) { - methodHandlers[method] = [method, handler, - thisPtr](llvm::json::Value rawParams, - Callback reply) { - llvm::Expected param = parse(rawParams, method, "request"); - if (!param) - return reply(param.takeError()); - (thisPtr->*handler)(*param, std::move(reply)); - }; - } - - template - void notification(llvm::StringLiteral method, ThisT *thisPtr, - void (ThisT::*handler)(const Param &)) { - notificationHandlers[method] = [method, handler, - thisPtr](llvm::json::Value rawParams) { - llvm::Expected param = parse(rawParams, method, "request"); - if (!param) - return llvm::consumeError(param.takeError()); - (thisPtr->*handler)(*param); - }; - } - -private: - template - using HandlerMap = llvm::StringMap>; - - HandlerMap notificationHandlers; - HandlerMap)> - methodHandlers; - - JSONTransport &transport; -}; +class MessageHandler; //===----------------------------------------------------------------------===// // JSONTransport @@ -185,6 +85,93 @@ private: bool prettyOutput; }; +//===----------------------------------------------------------------------===// +// MessageHandler +//===----------------------------------------------------------------------===// + +/// A Callback is a void function that accepts Expected. This is +/// accepted by functions that logically return T. +template +using Callback = llvm::unique_function)>; + +/// An OutgoingNotification is a function used for outgoing notifications +/// send to the client. +template +using OutgoingNotification = llvm::unique_function; + +/// A handler used to process the incoming transport messages. +class MessageHandler { +public: + MessageHandler(JSONTransport &transport) : transport(transport) {} + + bool onNotify(StringRef method, llvm::json::Value value); + bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id); + bool onReply(llvm::json::Value id, llvm::Expected result); + + template + static llvm::Expected parse(const llvm::json::Value &raw, + StringRef payloadName, StringRef payloadKind) { + T result; + llvm::json::Path::Root root; + if (fromJSON(raw, result, root)) + return std::move(result); + + // Dump the relevant parts of the broken message. + std::string context; + llvm::raw_string_ostream os(context); + root.printErrorContext(raw, os); + + // Report the error (e.g. to the client). + return llvm::make_error( + llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind, + fmt_consume(root.getError())), + ErrorCode::InvalidParams); + } + + template + void method(llvm::StringLiteral method, ThisT *thisPtr, + void (ThisT::*handler)(const Param &, Callback)) { + methodHandlers[method] = [method, handler, + thisPtr](llvm::json::Value rawParams, + Callback reply) { + llvm::Expected param = parse(rawParams, method, "request"); + if (!param) + return reply(param.takeError()); + (thisPtr->*handler)(*param, std::move(reply)); + }; + } + + template + void notification(llvm::StringLiteral method, ThisT *thisPtr, + void (ThisT::*handler)(const Param &)) { + notificationHandlers[method] = [method, handler, + thisPtr](llvm::json::Value rawParams) { + llvm::Expected param = parse(rawParams, method, "request"); + if (!param) + return llvm::consumeError(param.takeError()); + (thisPtr->*handler)(*param); + }; + } + + /// Create an OutgoingNotification object used for the given method. + template + OutgoingNotification outgoingNotification(llvm::StringLiteral method) { + return [&, method](const T ¶ms) { + transport.notify(method, llvm::json::Value(params)); + }; + } + +private: + template + using HandlerMap = llvm::StringMap>; + + HandlerMap notificationHandlers; + HandlerMap)> + methodHandlers; + + JSONTransport &transport; +}; + } // namespace lsp } // namespace mlir diff --git a/mlir/test/mlir-lsp-server/diagnostics.test b/mlir/test/mlir-lsp-server/diagnostics.test new file mode 100644 index 000000000000..d04183e60c94 --- /dev/null +++ b/mlir/test/mlir-lsp-server/diagnostics.test @@ -0,0 +1,35 @@ +// RUN: mlir-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"mlir","capabilities":{},"trace":"off"}} +// ----- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{ + "uri":"test:///foo.mlir", + "languageId":"mlir", + "version":1, + "text":"func ()" +}}} +// CHECK: "method": "textDocument/publishDiagnostics", +// CHECK-NEXT: "params": { +// CHECK-NEXT: "diagnostics": [ +// CHECK-NEXT: { +// CHECK-NEXT: "category": "Parse Error", +// CHECK-NEXT: "message": "custom op 'func' expected valid '@'-identifier for symbol name", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "end": { +// CHECK-NEXT: "character": 6, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: }, +// CHECK-NEXT: "start": { +// CHECK-NEXT: "character": 6, +// CHECK-NEXT: "line": 0 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "severity": 1, +// CHECK-NEXT: "source": "mlir" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "uri": "test:///foo.mlir" +// CHECK-NEXT: } +// ----- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +// ----- +{"jsonrpc":"2.0","method":"exit"}