27#include "../clang-tidy/ClangTidyModule.h"
28#include "../clang-tidy/ClangTidyModuleRegistry.h"
29#include "../clang-tidy/ClangTidyOptions.h"
30#include "../clang-tidy/GlobList.h"
52#include "clang-include-cleaner/Record.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"
86llvm::cl::opt<std::string> CheckTidyTime{
88 llvm::cl::desc(
"Print the overhead of checks matching this glob"),
90llvm::cl::opt<std::string> CheckFileLines{
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."),
98llvm::cl::opt<bool> CheckLocations{
101 "Runs certain features (e.g. hover) at each point in the file. "
103 llvm::cl::init(
true)};
104llvm::cl::opt<bool> CheckCompletion{
106 llvm::cl::desc(
"Run code-completion at each point (slow)"),
107 llvm::cl::init(
false)};
108llvm::cl::opt<bool> CheckWarnings{
110 llvm::cl::desc(
"Print warnings as well as errors"),
111 llvm::cl::init(
false)};
114unsigned showErrors(llvm::ArrayRef<Diag> Diags) {
116 for (
const auto &D : Diags) {
117 if (D.Severity >= DiagnosticsEngine::Error || CheckWarnings)
118 elog(
"[{0}] Line {1}: {2}", D.Name, D.Range.start.line + 1, D.Message);
119 if (D.Severity >= DiagnosticsEngine::Error)
125std::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(CTFactories);
130 std::vector<std::string> Result;
131 for (
const auto &
E : CTFactories)
132 if (G.contains(
E.getKey()))
133 Result.push_back(
E.getKey().str());
146 ClangdLSPServer::Options Opts;
148 tooling::CompileCommand Cmd;
149 std::unique_ptr<GlobalCompilationDatabase> BaseCDB;
150 std::unique_ptr<GlobalCompilationDatabase> CDB;
153 std::unique_ptr<CompilerInvocation> Invocation;
154 format::FormatStyle Style;
155 std::optional<ModulesBuilder> ModulesManager;
157 std::shared_ptr<const PreambleData>
Preamble;
158 std::optional<ParsedAST> AST;
165 Checker(llvm::StringRef File,
const ClangdLSPServer::Options &Opts)
166 : File(File), Opts(Opts) {}
169 bool buildCommand(
const ThreadsafeFS &TFS) {
170 log(
"Loading compilation database...");
171 DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
172 CDBOpts.CompileCommandsDir =
175 std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
177 Mangler.SystemIncludeExtractor =
179 if (Opts.ResourceDir)
180 Mangler.ResourceDir = *Opts.ResourceDir;
181 CDB = std::make_unique<OverlayCDB>(
182 BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler));
184 if (
auto TrueCmd = CDB->getCompileCommand(File)) {
185 Cmd = std::move(*TrueCmd);
186 log(
"Compile command {0} is: [{1}] {2}",
187 Cmd.Heuristic.empty() ?
"from CDB" : Cmd.Heuristic, Cmd.Directory,
190 Cmd = CDB->getFallbackCommand(File);
191 log(
"Generic fallback command is: [{0}] {1}", Cmd.Directory,
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;
205 Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
206 Inputs.Opts.PreambleParseForwardingFunctions =
207 Opts.PreambleParseForwardingFunctions;
209 Inputs.Contents = *Contents;
210 log(
"Imaginary source file contents:\n{0}", Inputs.Contents);
212 if (
auto Contents = TFS.view(std::nullopt)->getBufferForFile(File)) {
213 Inputs.Contents = Contents->get()->getBuffer().str();
215 elog(
"Couldn't read {0}: {1}", File, Contents.getError().message());
219 if (Opts.EnableExperimentalModulesSupport) {
221 ModulesManager.emplace(*CDB);
222 Inputs.ModulesManager = &*ModulesManager;
224 log(
"Parsing command...");
227 auto InvocationDiags = CaptureInvocationDiags.take();
228 ErrCount += showErrors(InvocationDiags);
229 log(
"internal (cc1) args are: {0}",
printArgv(CC1Args));
231 elog(
"Failed to parse command line");
244 log(
"Building preamble...");
246 File, *Invocation, Inputs,
true,
247 [&](CapturedASTCtx Ctx,
248 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
249 if (!Opts.BuildDynamicSymbolIndex)
251 log(
"Indexing headers...");
252 Index.updatePreamble(File,
"null", Ctx.getASTContext(),
253 Ctx.getPreprocessor(), *PI);
256 elog(
"Failed to build preamble");
261 log(
"Building AST...");
265 elog(
"Failed to build AST");
269 showErrors(AST->getDiagnostics().drop_front(
Preamble->Diags.size()));
271 if (Opts.BuildDynamicSymbolIndex) {
272 log(
"Indexing AST...");
273 Index.updateMain(File, *AST);
276 if (!CheckTidyTime.empty()) {
277 if (!CLANGD_TIDY_CHECKS) {
278 elog(
"-{0} requires -DCLANGD_TIDY_CHECKS!", CheckTidyTime.ArgStr);
282 elog(
"Timing clang-tidy checks in asserts-mode is not representative!");
297 void checkTidyTimes() {
298 double Stability = 0.03;
299 log(
"Timing AST build with individual clang-tidy checks (target accuracy "
303 using Duration = std::chrono::nanoseconds;
305 auto Time = [&](
auto &&Run) -> Duration {
306 llvm::sys::TimePoint<> Elapsed;
307 std::chrono::nanoseconds UserBegin, UserEnd, System;
308 llvm::sys::Process::GetTimeUsage(Elapsed, UserBegin, System);
310 llvm::sys::Process::GetTimeUsage(Elapsed, UserEnd, System);
311 return UserEnd - UserBegin;
313 auto Change = [&](Duration Exp, Duration Base) ->
double {
314 return (
double)(Exp.count() - Base.count()) / Base.count();
317 auto Build = [&](llvm::StringRef Checks) -> Duration {
318 TidyProvider CTProvider = [&](tidy::ClangTidyOptions &Opts,
320 Opts.Checks = Checks.str();
322 Inputs.ClangTidyProvider = CTProvider;
326 Duration Val = Time([&] {
329 vlog(
" Measured {0} ==> {1}", Checks, Val);
333 auto MedianTime = [&](llvm::StringRef Checks) -> Duration {
334 std::array<Duration, 5> Measurements;
335 for (
auto &
M : Measurements)
337 llvm::sort(Measurements);
338 return Measurements[Measurements.size() / 2];
340 Duration Baseline = MedianTime(
"-*");
341 log(
" Baseline = {0}", Baseline);
343 auto Measure = [&](llvm::StringRef Check) ->
double {
345 Duration Median = MedianTime((
"-*," + Check).str());
346 Duration NewBase = MedianTime(
"-*");
349 double DeltaFraction = Change(NewBase, Baseline);
351 vlog(
" Baseline = {0}", Baseline);
352 if (DeltaFraction < -Stability || DeltaFraction > Stability) {
353 elog(
" Speed unstable, discarding measurement.");
356 return Change(Median, Baseline);
360 for (
const auto& Check : listTidyChecks(CheckTidyTime)) {
362 vlog(
" Timing {0}", Check);
363 double Fraction = Measure(Check);
364 log(
" {0} = {1:P0}", Check, Fraction);
366 log(
"Finished individual clang-tidy checks");
369 Inputs.ClangTidyProvider = Opts.ClangTidyProvider;
373 void buildInlayHints(std::optional<Range> LineRange) {
374 log(
"Building inlay hints");
377 for (
const auto &Hint :
Hints) {
378 vlog(
" {0} {1} [{2}]", Hint.kind, Hint.position, [&] {
379 return llvm::join(llvm::map_range(Hint.label,
381 return llvm::formatv(
"{{{0}}", L);
388 void buildSemanticHighlighting(std::optional<Range> LineRange) {
389 log(
"Building semantic highlighting");
392 for (
const auto HL : Highlights)
393 if (!LineRange || LineRange->contains(HL.R))
394 vlog(
" {0} {1} {2}", HL.R, HL.Kind, HL.Modifiers);
398 void testLocationFeatures(std::optional<Range> LineRange) {
399 trace::Span Trace(
"testLocationFeatures");
400 log(
"Testing features at each token (may be slow in large files)");
401 auto &SM =
AST->getSourceManager();
402 auto SpelledTokens =
AST->getTokens().spelledTokens(SM.getMainFileID());
404 CodeCompleteOptions CCOpts = Opts.CodeComplete;
405 CCOpts.
Index = &Index;
407 for (
const auto &Tok : SpelledTokens) {
408 unsigned Start =
AST->getSourceManager().getFileOffset(Tok.location());
409 unsigned End = Start + Tok.length();
412 if (LineRange && !LineRange->contains(
Pos))
415 trace::Span Trace(
"Token");
421 vlog(
" {0} {1}",
Pos, Tok.text(
AST->getSourceManager()));
423 AST->getTokens(), Start, End);
424 Tweak::Selection Selection(&Index, *
AST, Start, End, std::move(Tree),
429 prepareTweaks(Selection, Opts.TweakFilter, Opts.FeatureModules);
431 &
AST->getSourceManager().getFileManager().getVirtualFileSystem();
432 for (
const auto &T : Tweaks) {
433 auto Result =
T->apply(Selection);
435 elog(
" tweak: {0} ==> FAIL: {1}",
T->id(), Result.takeError());
438 vlog(
" tweak: {0}",
T->id());
442 vlog(
" definition: {0}", Definitions);
445 vlog(
" hover: {0}", Hover.has_value());
448 vlog(
" documentHighlight: {0}", DocHighlights);
450 if (CheckCompletion) {
453 vlog(
" code completion: {0}",
454 CC.Completions.empty() ?
"<empty>" :
CC.Completions[0].Name);
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(0, Begin);
469 if (RangeStr.empty()) {
473 ParseError |= RangeStr.consumeInteger(0, End);
475 if (
ParseError || !RangeStr.empty() || Begin <= 0 || End < Begin) {
476 elog(
"Invalid --check-lines specified. Use Begin-End format, e.g. 3-17");
480 Position{
static_cast<int>(End), 0}};
483 llvm::SmallString<0> FakeFile;
484 std::optional<std::string> Contents;
486 llvm::sys::path::system_temp_directory(
false, FakeFile);
487 llvm::sys::path::append(FakeFile,
"test.cc");
494 auto xxx = std::string(N, 'x');
497 log("Testing on source file {0}", File);
500 std::vector<config::CompiledFragment>
506 if (CheckTidyTime.getNumOccurrences())
507 F.Diagnostics.ClangTidy.FastCheckFilter.emplace(
"None");
508 return {std::move(F).compile(
Diag)};
511 auto ConfigProvider =
512 config::Provider::combine({Opts.ConfigProvider, &OverrideConfig});
514 auto ContextProvider = ClangdServer::createConfiguredContextProvider(
515 ConfigProvider.get(),
nullptr);
520 Checker
C(File, Opts);
521 if (!
C.buildCommand(TFS) || !
C.buildInvocation(TFS, Contents) ||
524 C.buildInlayHints(LineRange);
525 C.buildSemanticHighlighting(LineRange);
527 C.testLocationFeatures(LineRange);
529 log(
"All checks completed, {0} errors",
C.ErrCount);
530 return C.ErrCount == 0;
const PreambleData & Preamble
std::vector< FixItHint > Hints
const google::protobuf::Message & M
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
static std::optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
WithContext replaces Context::current() with a provided scope.
A source of configuration fragments.
llvm::function_ref< void(const llvm::SMDiagnostic &)> DiagnosticCallback
Used to report problems in parsing or interpreting a config.
std::vector< HighlightingToken > getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens)
SystemIncludeExtractorFn getSystemIncludeExtractor(llvm::ArrayRef< std::string > QueryDriverGlobs)
Position offsetToPosition(llvm::StringRef Code, size_t Offset)
Turn an offset in Code into a [line, column] pair.
std::vector< DocumentHighlight > findDocumentHighlights(ParsedAST &AST, Position Pos)
Returns highlights for all usages of a symbol at Pos.
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
void vlog(const char *Fmt, Ts &&... Vals)
std::optional< HoverInfo > getHover(ParsedAST &AST, Position Pos, const format::FormatStyle &Style, const SymbolIndex *Index)
Get the hover information when hovering at Pos.
std::vector< LocatedSymbol > locateSymbolAt(ParsedAST &AST, Position Pos, const SymbolIndex *Index)
Get definition of symbol at a specified Pos.
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats)
Build a preamble for the new inputs unless an old one can be reused.
void log(const char *Fmt, Ts &&... Vals)
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
std::vector< std::unique_ptr< Tweak > > prepareTweaks(const Tweak::Selection &S, llvm::function_ref< bool(const Tweak &)> Filter, const FeatureModuleSet *Modules)
Calls prepare() on all tweaks that satisfy the filter, returning those that can run on the selection.
std::vector< InlayHint > inlayHints(ParsedAST &AST, std::optional< Range > RestrictRange)
Compute and return inlay hints for a file.
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
CodeCompleteResult codeComplete(PathRef FileName, Position Pos, const PreambleData *Preamble, const ParseInputs &ParseInput, CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind)
Gets code completions at a specified Pos in FileName.
bool check(llvm::StringRef File, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts)
void elog(const char *Fmt, Ts &&... Vals)
format::FormatStyle getFormatStyleForFile(llvm::StringRef File, llvm::StringRef Content, const ThreadsafeFS &TFS, bool FormatFile)
Choose the clang-format style we should apply to a certain file.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
const SymbolIndex * Index
If Index is set, it is used to augment the code completion results.
static CommandMangler detect()
std::optional< std::string > FixedCDBPath
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
CDBSearchSpec CDBSearch
Where to search for compilation databases for this file's flags.
struct clang::clangd::Config::@2 CompileFlags
Controls how the compile command for the current file is determined.
A top-level diagnostic that may have Notes and Fixes.
A chunk of configuration obtained from a config file, LSP, or elsewhere.
Describes the context used to evaluate configuration fragments.