| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- // 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
- #include "toolchain/language_server/context.h"
- #include <memory>
- #include <optional>
- #include <utility>
- #include "common/check.h"
- #include "common/raw_string_ostream.h"
- #include "llvm/TargetParser/Host.h"
- #include "toolchain/base/clang_invocation.h"
- #include "toolchain/base/shared_value_stores.h"
- #include "toolchain/check/check.h"
- #include "toolchain/diagnostics/consumer.h"
- #include "toolchain/diagnostics/diagnostic.h"
- #include "toolchain/diagnostics/emitter.h"
- #include "toolchain/lex/lex.h"
- #include "toolchain/lex/tokenized_buffer.h"
- #include "toolchain/parse/parse.h"
- #include "toolchain/parse/tree_and_subtrees.h"
- namespace Carbon::LanguageServer {
- namespace {
- // 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 DiagnosticConsumer : public Diagnostics::Consumer {
- public:
- // Initializes params with the target file information.
- explicit DiagnosticConsumer(Context* context,
- const clang::clangd::URIForFile& uri,
- std::optional<int64_t> version)
- : context_(context), params_{.uri = uri, .version = version} {}
- // Turns a diagnostic into an LSP diagnostic.
- auto HandleDiagnostic(Diagnostics::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;
- Diagnostics::StreamConsumer 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() -> const clang::clangd::PublishDiagnosticsParams& {
- 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 Diagnostics::Loc& 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(Diagnostics::Level 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 Diagnostics::Level::Error:
- return static_cast<int>(DiagnosticSeverity::Error);
- case Diagnostics::Level::Warning:
- return static_cast<int>(DiagnosticSeverity::Warning);
- default:
- CARBON_FATAL("Unexpected diagnostic level: {0}", level);
- }
- }
- Context* context_;
- clang::clangd::PublishDiagnosticsParams params_;
- };
- } // namespace
- auto Context::File::SetText(Context& context, std::optional<int64_t> version,
- llvm::StringRef text) -> void {
- // Clear state dependent on the source text.
- tree_and_subtrees_.reset();
- tree_.reset();
- tokens_.reset();
- value_stores_.reset();
- source_.reset();
- // A consumer to gather diagnostics for the file.
- DiagnosticConsumer 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.
- std::optional source =
- 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(uri_.file(), "", consumer);
- CARBON_CHECK(source, "Making an empty buffer should always succeed");
- }
- source_ = std::make_unique<SourceBuffer>(std::move(*source));
- value_stores_ = std::make_unique<SharedValueStores>();
- Lex::LexOptions lex_options;
- lex_options.consumer = &consumer;
- tokens_ = std::make_unique<Lex::TokenizedBuffer>(
- Lex::Lex(*value_stores_, *source_, lex_options));
- Parse::ParseOptions parse_options;
- parse_options.consumer = &consumer;
- parse_options.vlog_stream = context.vlog_stream();
- tree_ = std::make_unique<Parse::Tree>(Parse::Parse(*tokens_, parse_options));
- tree_and_subtrees_ =
- std::make_unique<Parse::TreeAndSubtrees>(*tokens_, *tree_);
- SemIR::File sem_ir(tree_.get(), SemIR::CheckIRId(0), tree_->packaging_decl(),
- *value_stores_, uri_.file().str());
- // TODO: Support cross-file checking when multiple files have edits.
- llvm::SmallVector<Check::Unit> units = {{{.consumer = &consumer,
- .value_stores = value_stores_.get(),
- .timings = nullptr,
- .sem_ir = &sem_ir,
- .total_ir_count = 1}}};
- auto getter = [this]() -> const Parse::TreeAndSubtrees& {
- return *tree_and_subtrees_;
- };
- // TODO: Include any unsaved files as an overlay on the real file system.
- llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs =
- llvm::vfs::getRealFileSystem();
- // TODO: Include the prelude. Make sure `total_ir_count` includes the files.
- Check::CheckParseTreesOptions check_options;
- check_options.vlog_stream = context.vlog_stream();
- auto getters =
- Parse::GetTreeAndSubtreesStore::MakeWithExplicitSize(1, getter);
- auto clang_invocation =
- BuildClangInvocation(consumer, fs, context.installation(),
- llvm::sys::getDefaultTargetTriple());
- Check::CheckParseTrees(units, getters, fs, check_options,
- std::move(clang_invocation));
- // Note we need to publish diagnostics even when empty.
- // TODO: Consider caching previously published diagnostics and only publishing
- // when they change.
- context.PublishDiagnostics(consumer.params());
- }
- auto Context::LookupFile(llvm::StringRef filename) -> File* {
- if (!filename.ends_with(".carbon")) {
- CARBON_DIAGNOSTIC(LanguageServerFileUnsupported, Warning,
- "non-Carbon file requested");
- file_emitter_.Emit(filename, LanguageServerFileUnsupported);
- return nullptr;
- }
- if (auto lookup_result = files().Lookup(filename)) {
- return &lookup_result.value();
- } else {
- CARBON_DIAGNOSTIC(LanguageServerFileUnknown, Warning,
- "unknown file requested");
- file_emitter_.Emit(filename, LanguageServerFileUnknown);
- return nullptr;
- }
- }
- } // namespace Carbon::LanguageServer
|