| 1 | //===--- Check.cpp - clangd self-diagnostics ------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Many basic problems can occur processing a file in clangd, e.g.: |
| 10 | // - system includes are not found |
| 11 | // - crash when indexing its AST |
| 12 | // clangd --check provides a simplified, isolated way to reproduce these, |
| 13 | // with no editor, LSP, threads, background indexing etc to contend with. |
| 14 | // |
| 15 | // One important use case is gathering information for bug reports. |
| 16 | // Another is reproducing crashes, and checking which setting prevent them. |
| 17 | // |
| 18 | // It simulates opening a file (determining compile command, parsing, indexing) |
| 19 | // and then running features at many locations. |
| 20 | // |
| 21 | // Currently it adds some basic logging of progress and results. |
| 22 | // We should consider extending it to also recognize common symptoms and |
| 23 | // recommend solutions (e.g. standard library installation issues). |
| 24 | // |
| 25 | //===----------------------------------------------------------------------===// |
| 26 | |
| 27 | #include "../clang-tidy/ClangTidyModule.h" |
| 28 | #include "../clang-tidy/ClangTidyModuleRegistry.h" |
| 29 | #include "../clang-tidy/ClangTidyOptions.h" |
| 30 | #include "../clang-tidy/GlobList.h" |
| 31 | #include "ClangdLSPServer.h" |
| 32 | #include "ClangdServer.h" |
| 33 | #include "CodeComplete.h" |
| 34 | #include "CompileCommands.h" |
| 35 | #include "Compiler.h" |
| 36 | #include "Config.h" |
| 37 | #include "ConfigFragment.h" |
| 38 | #include "ConfigProvider.h" |
| 39 | #include "Diagnostics.h" |
| 40 | #include "Feature.h" |
| 41 | #include "GlobalCompilationDatabase.h" |
| 42 | #include "Hover.h" |
| 43 | #include "InlayHints.h" |
| 44 | #include "ParsedAST.h" |
| 45 | #include "Preamble.h" |
| 46 | #include "Protocol.h" |
| 47 | #include "Selection.h" |
| 48 | #include "SemanticHighlighting.h" |
| 49 | #include "SourceCode.h" |
| 50 | #include "TidyProvider.h" |
| 51 | #include "XRefs.h" |
| 52 | #include "clang-include-cleaner/Record.h" |
| 53 | #include "index/FileIndex.h" |
| 54 | #include "refactor/Tweak.h" |
| 55 | #include "support/Context.h" |
| 56 | #include "support/Logger.h" |
| 57 | #include "support/ThreadsafeFS.h" |
| 58 | #include "support/Trace.h" |
| 59 | #include "clang/AST/ASTContext.h" |
| 60 | #include "clang/Basic/Diagnostic.h" |
| 61 | #include "clang/Basic/LLVM.h" |
| 62 | #include "clang/Format/Format.h" |
| 63 | #include "clang/Frontend/CompilerInvocation.h" |
| 64 | #include "clang/Tooling/CompilationDatabase.h" |
| 65 | #include "llvm/ADT/ArrayRef.h" |
| 66 | #include "llvm/ADT/STLExtras.h" |
| 67 | #include "llvm/ADT/SmallString.h" |
| 68 | #include "llvm/Support/Chrono.h" |
| 69 | #include "llvm/Support/CommandLine.h" |
| 70 | #include "llvm/Support/Path.h" |
| 71 | #include "llvm/Support/Process.h" |
| 72 | #include <array> |
| 73 | #include <chrono> |
| 74 | #include <cstdint> |
| 75 | #include <limits> |
| 76 | #include <memory> |
| 77 | #include <optional> |
| 78 | #include <utility> |
| 79 | #include <vector> |
| 80 | |
| 81 | namespace clang { |
| 82 | namespace clangd { |
| 83 | namespace { |
| 84 | |
| 85 | // These will never be shown in --help, ClangdMain doesn't list the category. |
| 86 | llvm::cl::opt<std::string> CheckTidyTime{ |
| 87 | "check-tidy-time" , |
| 88 | llvm::cl::desc("Print the overhead of checks matching this glob" ), |
| 89 | llvm::cl::init(Val: "" )}; |
| 90 | llvm::cl::opt<std::string> CheckFileLines{ |
| 91 | "check-lines" , |
| 92 | llvm::cl::desc( |
| 93 | "Limits the range of tokens in -check file on which " |
| 94 | "various features are tested. Example --check-lines=3-7 restricts " |
| 95 | "testing to lines 3 to 7 (inclusive) or --check-lines=5 to restrict " |
| 96 | "to one line. Default is testing entire file." ), |
| 97 | llvm::cl::init(Val: "" )}; |
| 98 | llvm::cl::opt<bool> CheckLocations{ |
| 99 | "check-locations" , |
| 100 | llvm::cl::desc( |
| 101 | "Runs certain features (e.g. hover) at each point in the file. " |
| 102 | "Somewhat slow." ), |
| 103 | llvm::cl::init(Val: true)}; |
| 104 | llvm::cl::opt<bool> CheckCompletion{ |
| 105 | "check-completion" , |
| 106 | llvm::cl::desc("Run code-completion at each point (slow)" ), |
| 107 | llvm::cl::init(Val: false)}; |
| 108 | llvm::cl::opt<bool> CheckWarnings{ |
| 109 | "check-warnings" , |
| 110 | llvm::cl::desc("Print warnings as well as errors" ), |
| 111 | llvm::cl::init(Val: false)}; |
| 112 | |
| 113 | // Print the diagnostics meeting severity threshold, and return count of errors. |
| 114 | unsigned showErrors(llvm::ArrayRef<Diag> Diags) { |
| 115 | unsigned ErrCount = 0; |
| 116 | for (const auto &D : Diags) { |
| 117 | if (D.Severity >= DiagnosticsEngine::Error || CheckWarnings) |
| 118 | elog(Fmt: "[{0}] Line {1}: {2}" , Vals: D.Name, Vals: D.Range.start.line + 1, Vals: D.Message); |
| 119 | if (D.Severity >= DiagnosticsEngine::Error) |
| 120 | ++ErrCount; |
| 121 | } |
| 122 | return ErrCount; |
| 123 | } |
| 124 | |
| 125 | std::vector<std::string> listTidyChecks(llvm::StringRef Glob) { |
| 126 | tidy::GlobList G(Glob); |
| 127 | tidy::ClangTidyCheckFactories CTFactories; |
| 128 | for (const auto &E : tidy::ClangTidyModuleRegistry::entries()) |
| 129 | E.instantiate()->addCheckFactories(CheckFactories&: CTFactories); |
| 130 | std::vector<std::string> Result; |
| 131 | for (const auto &E : CTFactories) |
| 132 | if (G.contains(S: E.getKey())) |
| 133 | Result.push_back(x: E.getKey().str()); |
| 134 | llvm::sort(C&: Result); |
| 135 | return Result; |
| 136 | } |
| 137 | |
| 138 | // This class is just a linear pipeline whose functions get called in sequence. |
| 139 | // Each exercises part of clangd's logic on our test file and logs results. |
| 140 | // Later steps depend on state built in earlier ones (such as the AST). |
| 141 | // Many steps can fatally fail (return false), then subsequent ones cannot run. |
| 142 | // Nonfatal failures are logged and tracked in ErrCount. |
| 143 | class Checker { |
| 144 | // from constructor |
| 145 | std::string File; |
| 146 | ClangdLSPServer::Options Opts; |
| 147 | // from buildCommand |
| 148 | tooling::CompileCommand Cmd; |
| 149 | std::unique_ptr<GlobalCompilationDatabase> BaseCDB; |
| 150 | std::unique_ptr<GlobalCompilationDatabase> CDB; |
| 151 | // from buildInvocation |
| 152 | ParseInputs Inputs; |
| 153 | std::unique_ptr<CompilerInvocation> Invocation; |
| 154 | format::FormatStyle Style; |
| 155 | std::optional<ModulesBuilder> ModulesManager; |
| 156 | // from buildAST |
| 157 | std::shared_ptr<const PreambleData> Preamble; |
| 158 | std::optional<ParsedAST> AST; |
| 159 | FileIndex Index; |
| 160 | |
| 161 | public: |
| 162 | // Number of non-fatal errors seen. |
| 163 | unsigned ErrCount = 0; |
| 164 | |
| 165 | Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts) |
| 166 | : File(File), Opts(Opts), Index(/*SupportContainedRefs=*/true) {} |
| 167 | |
| 168 | // Read compilation database and choose a compile command for the file. |
| 169 | bool buildCommand(const ThreadsafeFS &TFS) { |
| 170 | log(Fmt: "Loading compilation database..." ); |
| 171 | DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS); |
| 172 | CDBOpts.CompileCommandsDir = |
| 173 | Config::current().CompileFlags.CDBSearch.FixedCDBPath; |
| 174 | BaseCDB = |
| 175 | std::make_unique<DirectoryBasedGlobalCompilationDatabase>(args&: CDBOpts); |
| 176 | auto Mangler = CommandMangler::detect(); |
| 177 | Mangler.SystemIncludeExtractor = |
| 178 | getSystemIncludeExtractor(QueryDriverGlobs: llvm::ArrayRef(Opts.QueryDriverGlobs)); |
| 179 | if (Opts.ResourceDir) |
| 180 | Mangler.ResourceDir = *Opts.ResourceDir; |
| 181 | CDB = std::make_unique<OverlayCDB>( |
| 182 | args: BaseCDB.get(), args: std::vector<std::string>{}, args: std::move(Mangler)); |
| 183 | |
| 184 | if (auto TrueCmd = CDB->getCompileCommand(File)) { |
| 185 | Cmd = std::move(*TrueCmd); |
| 186 | log(Fmt: "Compile command {0} is: [{1}] {2}" , |
| 187 | Vals: Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Vals&: Cmd.Directory, |
| 188 | Vals: printArgv(Args: Cmd.CommandLine)); |
| 189 | } else { |
| 190 | Cmd = CDB->getFallbackCommand(File); |
| 191 | log(Fmt: "Generic fallback command is: [{0}] {1}" , Vals&: Cmd.Directory, |
| 192 | Vals: printArgv(Args: Cmd.CommandLine)); |
| 193 | } |
| 194 | |
| 195 | return true; |
| 196 | } |
| 197 | |
| 198 | // Prepare inputs and build CompilerInvocation (parsed compile command). |
| 199 | bool buildInvocation(const ThreadsafeFS &TFS, |
| 200 | std::optional<std::string> Contents) { |
| 201 | StoreDiags CaptureInvocationDiags; |
| 202 | std::vector<std::string> CC1Args; |
| 203 | Inputs.CompileCommand = Cmd; |
| 204 | Inputs.TFS = &TFS; |
| 205 | Inputs.ClangTidyProvider = Opts.ClangTidyProvider; |
| 206 | Inputs.Opts.PreambleParseForwardingFunctions = |
| 207 | Opts.PreambleParseForwardingFunctions; |
| 208 | if (Contents) { |
| 209 | Inputs.Contents = *Contents; |
| 210 | log(Fmt: "Imaginary source file contents:\n{0}" , Vals&: Inputs.Contents); |
| 211 | } else { |
| 212 | if (auto Contents = TFS.view(CWD: std::nullopt)->getBufferForFile(Name: File)) { |
| 213 | Inputs.Contents = Contents->get()->getBuffer().str(); |
| 214 | } else { |
| 215 | elog(Fmt: "Couldn't read {0}: {1}" , Vals&: File, Vals: Contents.getError().message()); |
| 216 | return false; |
| 217 | } |
| 218 | } |
| 219 | if (Opts.EnableExperimentalModulesSupport) { |
| 220 | if (!ModulesManager) |
| 221 | ModulesManager.emplace(args&: *CDB); |
| 222 | Inputs.ModulesManager = &*ModulesManager; |
| 223 | } |
| 224 | log(Fmt: "Parsing command..." ); |
| 225 | Invocation = |
| 226 | buildCompilerInvocation(Inputs, D&: CaptureInvocationDiags, CC1Args: &CC1Args); |
| 227 | auto InvocationDiags = CaptureInvocationDiags.take(); |
| 228 | ErrCount += showErrors(Diags: InvocationDiags); |
| 229 | log(Fmt: "internal (cc1) args are: {0}" , Vals: printArgv(Args: CC1Args)); |
| 230 | if (!Invocation) { |
| 231 | elog(Fmt: "Failed to parse command line" ); |
| 232 | return false; |
| 233 | } |
| 234 | |
| 235 | // FIXME: Check that resource-dir/built-in-headers exist? |
| 236 | |
| 237 | Style = getFormatStyleForFile(File, Content: Inputs.Contents, TFS, FormatFile: false); |
| 238 | |
| 239 | return true; |
| 240 | } |
| 241 | |
| 242 | // Build preamble and AST, and index them. |
| 243 | bool buildAST() { |
| 244 | log(Fmt: "Building preamble..." ); |
| 245 | Preamble = buildPreamble( |
| 246 | FileName: File, CI: *Invocation, Inputs, /*StoreInMemory=*/true, |
| 247 | PreambleCallback: [&](CapturedASTCtx Ctx, |
| 248 | std::shared_ptr<const include_cleaner::PragmaIncludes> PI) { |
| 249 | if (!Opts.BuildDynamicSymbolIndex) |
| 250 | return; |
| 251 | log(Fmt: "Indexing headers..." ); |
| 252 | Index.updatePreamble(Path: File, /*Version=*/"null" , AST&: Ctx.getASTContext(), |
| 253 | PP&: Ctx.getPreprocessor(), PI: *PI); |
| 254 | }); |
| 255 | if (!Preamble) { |
| 256 | elog(Fmt: "Failed to build preamble" ); |
| 257 | return false; |
| 258 | } |
| 259 | ErrCount += showErrors(Diags: Preamble->Diags); |
| 260 | |
| 261 | log(Fmt: "Building AST..." ); |
| 262 | AST = ParsedAST::build(Filename: File, Inputs, CI: std::move(Invocation), |
| 263 | /*InvocationDiags=*/CompilerInvocationDiags: std::vector<Diag>{}, Preamble); |
| 264 | if (!AST) { |
| 265 | elog(Fmt: "Failed to build AST" ); |
| 266 | return false; |
| 267 | } |
| 268 | ErrCount += |
| 269 | showErrors(Diags: AST->getDiagnostics().drop_front(N: Preamble->Diags.size())); |
| 270 | |
| 271 | if (Opts.BuildDynamicSymbolIndex) { |
| 272 | log(Fmt: "Indexing AST..." ); |
| 273 | Index.updateMain(Path: File, AST&: *AST); |
| 274 | } |
| 275 | |
| 276 | if (!CheckTidyTime.empty()) { |
| 277 | if (!CLANGD_TIDY_CHECKS) { |
| 278 | elog(Fmt: "-{0} requires -DCLANGD_TIDY_CHECKS!" , Vals&: CheckTidyTime.ArgStr); |
| 279 | return false; |
| 280 | } |
| 281 | #ifndef NDEBUG |
| 282 | elog(Fmt: "Timing clang-tidy checks in asserts-mode is not representative!" ); |
| 283 | #endif |
| 284 | checkTidyTimes(); |
| 285 | } |
| 286 | |
| 287 | return true; |
| 288 | } |
| 289 | |
| 290 | // For each check foo, we want to build with checks=-* and checks=-*,foo. |
| 291 | // (We do a full build rather than just AST matchers to meausre PPCallbacks). |
| 292 | // |
| 293 | // However, performance has both random noise and systematic changes, such as |
| 294 | // step-function slowdowns due to CPU scaling. |
| 295 | // We take the median of 5 measurements, and after every check discard the |
| 296 | // measurement if the baseline changed by >3%. |
| 297 | void checkTidyTimes() { |
| 298 | double Stability = 0.03; |
| 299 | log(Fmt: "Timing AST build with individual clang-tidy checks (target accuracy " |
| 300 | "{0:P0})" , |
| 301 | Vals&: Stability); |
| 302 | |
| 303 | using Duration = std::chrono::nanoseconds; |
| 304 | // Measure time elapsed by a block of code. Currently: user CPU time. |
| 305 | auto Time = [&](auto &&Run) -> Duration { |
| 306 | llvm::sys::TimePoint<> Elapsed; |
| 307 | std::chrono::nanoseconds UserBegin, UserEnd, System; |
| 308 | llvm::sys::Process::GetTimeUsage(elapsed&: Elapsed, user_time&: UserBegin, sys_time&: System); |
| 309 | Run(); |
| 310 | llvm::sys::Process::GetTimeUsage(elapsed&: Elapsed, user_time&: UserEnd, sys_time&: System); |
| 311 | return UserEnd - UserBegin; |
| 312 | }; |
| 313 | auto Change = [&](Duration Exp, Duration Base) -> double { |
| 314 | return (double)(Exp.count() - Base.count()) / Base.count(); |
| 315 | }; |
| 316 | // Build ParsedAST with a fixed check glob, and return the time taken. |
| 317 | auto Build = [&](llvm::StringRef Checks) -> Duration { |
| 318 | TidyProvider CTProvider = [&](tidy::ClangTidyOptions &Opts, |
| 319 | llvm::StringRef) { |
| 320 | Opts.Checks = Checks.str(); |
| 321 | }; |
| 322 | Inputs.ClangTidyProvider = CTProvider; |
| 323 | // Sigh, can't reuse the CompilerInvocation. |
| 324 | IgnoringDiagConsumer IgnoreDiags; |
| 325 | auto Invocation = buildCompilerInvocation(Inputs, D&: IgnoreDiags); |
| 326 | Duration Val = Time([&] { |
| 327 | ParsedAST::build(Filename: File, Inputs, CI: std::move(Invocation), CompilerInvocationDiags: {}, Preamble); |
| 328 | }); |
| 329 | vlog(Fmt: " Measured {0} ==> {1}" , Vals&: Checks, Vals&: Val); |
| 330 | return Val; |
| 331 | }; |
| 332 | // Measure several times, return the median. |
| 333 | auto MedianTime = [&](llvm::StringRef Checks) -> Duration { |
| 334 | std::array<Duration, 5> Measurements; |
| 335 | for (auto &M : Measurements) |
| 336 | M = Build(Checks); |
| 337 | llvm::sort(C&: Measurements); |
| 338 | return Measurements[Measurements.size() / 2]; |
| 339 | }; |
| 340 | Duration Baseline = MedianTime("-*" ); |
| 341 | log(Fmt: " Baseline = {0}" , Vals&: Baseline); |
| 342 | // Attempt to time a check, may update Baseline if it is unstable. |
| 343 | auto Measure = [&](llvm::StringRef Check) -> double { |
| 344 | for (;;) { |
| 345 | Duration Median = MedianTime(("-*," + Check).str()); |
| 346 | Duration NewBase = MedianTime("-*" ); |
| 347 | |
| 348 | // Value only usable if baseline is fairly consistent before/after. |
| 349 | double DeltaFraction = Change(NewBase, Baseline); |
| 350 | Baseline = NewBase; |
| 351 | vlog(Fmt: " Baseline = {0}" , Vals&: Baseline); |
| 352 | if (DeltaFraction < -Stability || DeltaFraction > Stability) { |
| 353 | elog(Fmt: " Speed unstable, discarding measurement." ); |
| 354 | continue; |
| 355 | } |
| 356 | return Change(Median, Baseline); |
| 357 | } |
| 358 | }; |
| 359 | |
| 360 | for (const auto& Check : listTidyChecks(Glob: CheckTidyTime)) { |
| 361 | // vlog the check name in case we crash! |
| 362 | vlog(Fmt: " Timing {0}" , Vals: Check); |
| 363 | double Fraction = Measure(Check); |
| 364 | log(Fmt: " {0} = {1:P0}" , Vals: Check, Vals&: Fraction); |
| 365 | } |
| 366 | log(Fmt: "Finished individual clang-tidy checks" ); |
| 367 | |
| 368 | // Restore old options. |
| 369 | Inputs.ClangTidyProvider = Opts.ClangTidyProvider; |
| 370 | } |
| 371 | |
| 372 | // Build Inlay Hints for the entire AST or the specified range |
| 373 | void buildInlayHints(std::optional<Range> LineRange) { |
| 374 | log(Fmt: "Building inlay hints" ); |
| 375 | auto Hints = inlayHints(AST&: *AST, RestrictRange: LineRange); |
| 376 | |
| 377 | for (const auto &Hint : Hints) { |
| 378 | vlog(Fmt: " {0} {1} [{2}]" , Vals: Hint.kind, Vals: Hint.position, Vals: [&] { |
| 379 | return llvm::join(R: llvm::map_range(C: Hint.label, |
| 380 | F: [&](auto &L) { |
| 381 | return llvm::formatv("{{{0}}" , L); |
| 382 | }), |
| 383 | Separator: ", " ); |
| 384 | }()); |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | void buildSemanticHighlighting(std::optional<Range> LineRange) { |
| 389 | log(Fmt: "Building semantic highlighting" ); |
| 390 | auto Highlights = |
| 391 | getSemanticHighlightings(AST&: *AST, /*IncludeInactiveRegionTokens=*/true); |
| 392 | for (const auto HL : Highlights) |
| 393 | if (!LineRange || LineRange->contains(Rng: HL.R)) |
| 394 | vlog(Fmt: " {0} {1} {2}" , Vals: HL.R, Vals: HL.Kind, Vals: HL.Modifiers); |
| 395 | } |
| 396 | |
| 397 | // Run AST-based features at each token in the file. |
| 398 | void testLocationFeatures(std::optional<Range> LineRange) { |
| 399 | trace::Span Trace("testLocationFeatures" ); |
| 400 | log(Fmt: "Testing features at each token (may be slow in large files)" ); |
| 401 | auto &SM = AST->getSourceManager(); |
| 402 | auto SpelledTokens = AST->getTokens().spelledTokens(FID: SM.getMainFileID()); |
| 403 | |
| 404 | CodeCompleteOptions CCOpts = Opts.CodeComplete; |
| 405 | CCOpts.Index = &Index; |
| 406 | |
| 407 | for (const auto &Tok : SpelledTokens) { |
| 408 | unsigned Start = AST->getSourceManager().getFileOffset(SpellingLoc: Tok.location()); |
| 409 | unsigned End = Start + Tok.length(); |
| 410 | Position Pos = offsetToPosition(Code: Inputs.Contents, Offset: Start); |
| 411 | |
| 412 | if (LineRange && !LineRange->contains(Pos)) |
| 413 | continue; |
| 414 | |
| 415 | trace::Span Trace("Token" ); |
| 416 | SPAN_ATTACH(Trace, "pos" , Pos); |
| 417 | SPAN_ATTACH(Trace, "text" , Tok.text(AST->getSourceManager())); |
| 418 | |
| 419 | // FIXME: dumping the tokens may leak sensitive code into bug reports. |
| 420 | // Add an option to turn this off, once we decide how options work. |
| 421 | vlog(Fmt: " {0} {1}" , Vals&: Pos, Vals: Tok.text(SM: AST->getSourceManager())); |
| 422 | auto Tree = SelectionTree::createRight(AST&: AST->getASTContext(), |
| 423 | Tokens: AST->getTokens(), Begin: Start, End); |
| 424 | Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree), |
| 425 | nullptr); |
| 426 | // FS is only populated when applying a tweak, not during prepare as |
| 427 | // prepare should not do any I/O to be fast. |
| 428 | auto Tweaks = |
| 429 | prepareTweaks(S: Selection, Filter: Opts.TweakFilter, Modules: Opts.FeatureModules); |
| 430 | Selection.FS = |
| 431 | &AST->getSourceManager().getFileManager().getVirtualFileSystem(); |
| 432 | for (const auto &T : Tweaks) { |
| 433 | auto Result = T->apply(Sel: Selection); |
| 434 | if (!Result) { |
| 435 | elog(Fmt: " tweak: {0} ==> FAIL: {1}" , Vals: T->id(), Vals: Result.takeError()); |
| 436 | ++ErrCount; |
| 437 | } else { |
| 438 | vlog(Fmt: " tweak: {0}" , Vals: T->id()); |
| 439 | } |
| 440 | } |
| 441 | unsigned Definitions = locateSymbolAt(AST&: *AST, Pos, Index: &Index).size(); |
| 442 | vlog(Fmt: " definition: {0}" , Vals&: Definitions); |
| 443 | |
| 444 | auto Hover = getHover(AST&: *AST, Pos, Style, Index: &Index); |
| 445 | vlog(Fmt: " hover: {0}" , Vals: Hover.has_value()); |
| 446 | |
| 447 | unsigned DocHighlights = findDocumentHighlights(AST&: *AST, Pos).size(); |
| 448 | vlog(Fmt: " documentHighlight: {0}" , Vals&: DocHighlights); |
| 449 | |
| 450 | if (CheckCompletion) { |
| 451 | Position EndPos = offsetToPosition(Code: Inputs.Contents, Offset: End); |
| 452 | auto CC = codeComplete(FileName: File, Pos: EndPos, Preamble: Preamble.get(), ParseInput: Inputs, Opts: CCOpts); |
| 453 | vlog(Fmt: " code completion: {0}" , |
| 454 | Vals: CC.Completions.empty() ? "<empty>" : CC.Completions[0].Name); |
| 455 | } |
| 456 | } |
| 457 | } |
| 458 | }; |
| 459 | |
| 460 | } // namespace |
| 461 | |
| 462 | bool check(llvm::StringRef File, const ThreadsafeFS &TFS, |
| 463 | const ClangdLSPServer::Options &Opts) { |
| 464 | std::optional<Range> LineRange; |
| 465 | if (!CheckFileLines.empty()) { |
| 466 | uint32_t Begin = 0, End = std::numeric_limits<uint32_t>::max(); |
| 467 | StringRef RangeStr(CheckFileLines); |
| 468 | bool ParseError = RangeStr.consumeInteger(Radix: 0, Result&: Begin); |
| 469 | if (RangeStr.empty()) { |
| 470 | End = Begin; |
| 471 | } else { |
| 472 | ParseError |= !RangeStr.consume_front(Prefix: "-" ); |
| 473 | ParseError |= RangeStr.consumeInteger(Radix: 0, Result&: End); |
| 474 | } |
| 475 | if (ParseError || !RangeStr.empty() || Begin <= 0 || End < Begin) { |
| 476 | elog(Fmt: "Invalid --check-lines specified. Use Begin-End format, e.g. 3-17" ); |
| 477 | return false; |
| 478 | } |
| 479 | LineRange = Range{.start: Position{.line: static_cast<int>(Begin - 1), .character: 0}, |
| 480 | .end: Position{.line: static_cast<int>(End), .character: 0}}; |
| 481 | } |
| 482 | |
| 483 | llvm::SmallString<0> FakeFile; |
| 484 | std::optional<std::string> Contents; |
| 485 | if (File.empty()) { |
| 486 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: FakeFile); |
| 487 | llvm::sys::path::append(path&: FakeFile, a: "test.cc" ); |
| 488 | File = FakeFile; |
| 489 | Contents = R"cpp( |
| 490 | #include <stddef.h> |
| 491 | #include <string> |
| 492 | |
| 493 | size_t N = 50; |
| 494 | auto xxx = std::string(N, 'x'); |
| 495 | )cpp" ; |
| 496 | } |
| 497 | log(Fmt: "Testing on source file {0}" , Vals&: File); |
| 498 | |
| 499 | class OverrideConfigProvider : public config::Provider { |
| 500 | std::vector<config::CompiledFragment> |
| 501 | getFragments(const config::Params &, |
| 502 | config::DiagnosticCallback Diag) const override { |
| 503 | config::Fragment F; |
| 504 | // If we're timing clang-tidy checks, implicitly disabling the slow ones |
| 505 | // is counterproductive! |
| 506 | if (CheckTidyTime.getNumOccurrences()) |
| 507 | F.Diagnostics.ClangTidy.FastCheckFilter.emplace(args: "None" ); |
| 508 | return {std::move(F).compile(Diag)}; |
| 509 | } |
| 510 | } OverrideConfig; |
| 511 | auto ConfigProvider = |
| 512 | config::Provider::combine({Opts.ConfigProvider, &OverrideConfig}); |
| 513 | |
| 514 | auto ContextProvider = ClangdServer::createConfiguredContextProvider( |
| 515 | Provider: ConfigProvider.get(), nullptr); |
| 516 | WithContext Ctx(ContextProvider( |
| 517 | FakeFile.empty() |
| 518 | ? File |
| 519 | : /*Don't turn on local configs for an arbitrary temp path.*/ "" )); |
| 520 | Checker C(File, Opts); |
| 521 | if (!C.buildCommand(TFS) || !C.buildInvocation(TFS, Contents) || |
| 522 | !C.buildAST()) |
| 523 | return false; |
| 524 | C.buildInlayHints(LineRange); |
| 525 | C.buildSemanticHighlighting(LineRange); |
| 526 | if (CheckLocations) |
| 527 | C.testLocationFeatures(LineRange); |
| 528 | |
| 529 | log(Fmt: "All checks completed, {0} errors" , Vals&: C.ErrCount); |
| 530 | return C.ErrCount == 0; |
| 531 | } |
| 532 | |
| 533 | } // namespace clangd |
| 534 | } // namespace clang |
| 535 | |