[clang-tools-extra] 73fdd99 - [clangd] Implement clang-tidy options from config

Nathan James via cfe-commits cfe-commits at lists.llvm.org
Wed Nov 25 10:35:49 PST 2020


Author: Nathan James
Date: 2020-11-25T18:35:35Z
New Revision: 73fdd998701cce3aa6c4d8d2a73ab97351a0313b

URL: https://github.com/llvm/llvm-project/commit/73fdd998701cce3aa6c4d8d2a73ab97351a0313b
DIFF: https://github.com/llvm/llvm-project/commit/73fdd998701cce3aa6c4d8d2a73ab97351a0313b.diff

LOG: [clangd] Implement clang-tidy options from config

Added some new ClangTidyOptionsProvider like classes designed for clangd work flow.
These providers are designed to source the options on the worker thread but in a thread safe manner.
This is done through making the options getter take a pointer to the filesystem used by the worker thread which natuarally is from a ThreadsafeFS.
Internal caching in the providers is also guarded.

The providers don't inherit from `ClangTidyOptionsProvider` instead they share a base class which is able to create a provider for the `ClangTidyContext` using a specific FileSystem.
This approach means one provider can be used for multiple contexts even though `ClangTidyContext` owns its provider.

Depends on D90531

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D91029

Added: 
    clang-tools-extra/clangd/TidyProvider.cpp
    clang-tools-extra/clangd/TidyProvider.h

Modified: 
    clang-tools-extra/clangd/CMakeLists.txt
    clang-tools-extra/clangd/ClangdServer.cpp
    clang-tools-extra/clangd/ClangdServer.h
    clang-tools-extra/clangd/Compiler.h
    clang-tools-extra/clangd/ParsedAST.cpp
    clang-tools-extra/clangd/tool/Check.cpp
    clang-tools-extra/clangd/tool/ClangdMain.cpp
    clang-tools-extra/clangd/unittests/ClangdTests.cpp
    clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
    clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
    clang-tools-extra/clangd/unittests/TestTU.cpp
    clang-tools-extra/clangd/unittests/TestTU.h

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 2ce5d31e623e..919457f216c1 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -81,6 +81,7 @@ add_clang_library(clangDaemon
   SemanticSelection.cpp
   SourceCode.cpp
   QueryDriverDatabase.cpp
+  TidyProvider.cpp
   TUScheduler.cpp
   URI.cpp
   XRefs.cpp

diff  --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 502078c776db..aa19a9485e17 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -114,40 +114,6 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
   bool TheiaSemanticHighlighting;
 };
 
-// Set of clang-tidy checks that don't work well in clangd, either due to
-// crashes or false positives.
-// Returns a clang-tidy filter string: -check1,-check2.
-llvm::StringRef getUnusableTidyChecks() {
-  static const std::string FalsePositives =
-      llvm::join_items(", ",
-                       // Check relies on seeing ifndef/define/endif directives,
-                       // clangd doesn't replay those when using a preamble.
-                       "-llvm-header-guard");
-  static const std::string CrashingChecks =
-      llvm::join_items(", ",
-                       // Check can choke on invalid (intermediate) c++ code,
-                       // which is often the case when clangd tries to build an
-                       // AST.
-                       "-bugprone-use-after-move");
-  static const std::string UnusableTidyChecks =
-      llvm::join_items(", ", FalsePositives, CrashingChecks);
-  return UnusableTidyChecks;
-}
-
-// Returns a clang-tidy options string: check1,check2.
-llvm::StringRef getDefaultTidyChecks() {
-  // These default checks are chosen for:
-  //  - low false-positive rate
-  //  - providing a lot of value
-  //  - being reasonably efficient
-  static const std::string DefaultChecks = llvm::join_items(
-      ",", "readability-misleading-indentation", "readability-deleted-default",
-      "bugprone-integer-division", "bugprone-sizeof-expression",
-      "bugprone-suspicious-missing-comma", "bugprone-unused-raii",
-      "bugprone-unused-return-value", "misc-unused-using-decls",
-      "misc-unused-alias-decls", "misc-definitions-in-headers");
-  return DefaultChecks;
-}
 } // namespace
 
 ClangdServer::Options ClangdServer::optsForTest() {
@@ -178,7 +144,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
                      ? new FileIndex(Opts.HeavyweightDynamicSymbolIndex,
                                      Opts.CollectMainFileRefs)
                      : nullptr),
-      GetClangTidyOptions(Opts.GetClangTidyOptions),
+      ClangTidyProvider(Opts.ClangTidyProvider),
       SuggestMissingIncludes(Opts.SuggestMissingIncludes),
       BuildRecoveryAST(Opts.BuildRecoveryAST),
       PreserveRecoveryASTType(Opts.PreserveRecoveryASTType),
@@ -236,20 +202,6 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
                                llvm::StringRef Version,
                                WantDiagnostics WantDiags, bool ForceRebuild) {
   ParseOptions Opts;
-  Opts.ClangTidyOpts = tidy::ClangTidyOptions::getDefaults();
-  // FIXME: call tidy options builder on the worker thread, it can do IO.
-  if (GetClangTidyOptions)
-    Opts.ClangTidyOpts =
-        GetClangTidyOptions(*TFS.view(/*CWD=*/llvm::None), File);
-  if (Opts.ClangTidyOpts.Checks.hasValue()) {
-    // If the set of checks was configured, make sure clangd incompatible ones
-    // are disabled.
-    Opts.ClangTidyOpts.Checks = llvm::join_items(
-        ", ", *Opts.ClangTidyOpts.Checks, getUnusableTidyChecks());
-  } else {
-    // Otherwise provide a nice set of defaults.
-    Opts.ClangTidyOpts.Checks = getDefaultTidyChecks().str();
-  }
   Opts.SuggestMissingIncludes = SuggestMissingIncludes;
 
   // Compile command is set asynchronously during update, as it can be slow.
@@ -260,6 +212,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
   Inputs.ForceRebuild = ForceRebuild;
   Inputs.Opts = std::move(Opts);
   Inputs.Index = Index;
+  Inputs.ClangTidyProvider = ClangTidyProvider;
   Inputs.Opts.BuildRecoveryAST = BuildRecoveryAST;
   Inputs.Opts.PreserveRecoveryASTType = PreserveRecoveryASTType;
   bool NewFile = WorkScheduler.update(File, Inputs, WantDiags);

diff  --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 18c35e701e5b..b6a1bd757894 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -41,13 +41,6 @@
 
 namespace clang {
 namespace clangd {
-
-/// When set, used by ClangdServer to get clang-tidy options for each particular
-/// file. Must be thread-safe. We use this instead of ClangTidyOptionsProvider
-/// to allow reading tidy configs from the VFS used for parsing.
-using ClangTidyOptionsBuilder = std::function<tidy::ClangTidyOptions(
-    llvm::vfs::FileSystem &, llvm::StringRef /*File*/)>;
-
 /// Manages a collection of source files and derived data (ASTs, indexes),
 /// and provides language-aware features such as code completion.
 ///
@@ -121,12 +114,9 @@ class ClangdServer {
     /// If set, queried to obtain the configuration to handle each request.
     config::Provider *ConfigProvider = nullptr;
 
-    /// If set, enable clang-tidy in clangd and use to it get clang-tidy
-    /// configurations for a particular file.
-    /// Clangd supports only a small subset of ClangTidyOptions, these options
-    /// (Checks, CheckOptions) are about which clang-tidy checks will be
-    /// enabled.
-    ClangTidyOptionsBuilder GetClangTidyOptions;
+    /// The Options provider to use when running clang-tidy. If null, clang-tidy
+    /// checks will be disabled.
+    TidyProviderRef ClangTidyProvider;
 
     /// If true, force -frecovery-ast flag.
     /// If false, respect the value in clang.
@@ -389,7 +379,7 @@ class ClangdServer {
   std::vector<std::unique_ptr<SymbolIndex>> MergedIdx;
 
   // When set, provides clang-tidy options for a specific file.
-  ClangTidyOptionsBuilder GetClangTidyOptions;
+  TidyProviderRef ClangTidyProvider;
 
   // If this is true, suggest include insertion fixes for diagnostic errors that
   // can be caused by missing includes (e.g. member access in incomplete type).

diff  --git a/clang-tools-extra/clangd/Compiler.h b/clang-tools-extra/clangd/Compiler.h
index 740a9c3f6f00..c9c75625727b 100644
--- a/clang-tools-extra/clangd/Compiler.h
+++ b/clang-tools-extra/clangd/Compiler.h
@@ -15,8 +15,8 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H
 
-#include "../clang-tidy/ClangTidyOptions.h"
 #include "GlobalCompilationDatabase.h"
+#include "TidyProvider.h"
 #include "index/Index.h"
 #include "support/ThreadsafeFS.h"
 #include "clang/Frontend/CompilerInstance.h"
@@ -37,7 +37,6 @@ class IgnoreDiagnostics : public DiagnosticConsumer {
 
 // Options to run clang e.g. when parsing AST.
 struct ParseOptions {
-  tidy::ClangTidyOptions ClangTidyOpts;
   bool SuggestMissingIncludes = false;
   bool BuildRecoveryAST = false;
   bool PreserveRecoveryASTType = false;
@@ -56,6 +55,7 @@ struct ParseInputs {
   // Used to recover from diagnostics (e.g. find missing includes for symbol).
   const SymbolIndex *Index = nullptr;
   ParseOptions Opts = ParseOptions();
+  TidyProviderRef ClangTidyProvider = {};
 };
 
 /// Builds compiler invocation that could be used to build AST or preamble.

diff  --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index d3c14e6f3c87..228db29b2be3 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -17,6 +17,7 @@
 #include "IncludeFixer.h"
 #include "Preamble.h"
 #include "SourceCode.h"
+#include "TidyProvider.h"
 #include "index/CanonicalIncludes.h"
 #include "index/Index.h"
 #include "support/Logger.h"
@@ -292,13 +293,15 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
   llvm::Optional<tidy::ClangTidyContext> CTContext;
   {
     trace::Span Tracer("ClangTidyInit");
+    tidy::ClangTidyOptions ClangTidyOpts =
+        getTidyOptionsForFile(Inputs.ClangTidyProvider, Filename);
     dlog("ClangTidy configuration for file {0}: {1}", Filename,
-         tidy::configurationAsText(Inputs.Opts.ClangTidyOpts));
+         tidy::configurationAsText(ClangTidyOpts));
     tidy::ClangTidyCheckFactories CTFactories;
     for (const auto &E : tidy::ClangTidyModuleRegistry::entries())
       E.instantiate()->addCheckFactories(CTFactories);
     CTContext.emplace(std::make_unique<tidy::DefaultOptionsProvider>(
-        tidy::ClangTidyGlobalOptions(), Inputs.Opts.ClangTidyOpts));
+        tidy::ClangTidyGlobalOptions(), ClangTidyOpts));
     CTContext->setDiagnosticsEngine(&Clang->getDiagnostics());
     CTContext->setASTContext(&Clang->getASTContext());
     CTContext->setCurrentFile(Filename);

diff  --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp
new file mode 100644
index 000000000000..1fb79f3abbce
--- /dev/null
+++ b/clang-tools-extra/clangd/TidyProvider.cpp
@@ -0,0 +1,237 @@
+//===--- TidyProvider.cpp - create options for running clang-tidy----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TidyProvider.h"
+#include "Config.h"
+#include "support/Logger.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include <memory>
+
+namespace clang {
+namespace clangd {
+
+static void mergeCheckList(llvm::Optional<std::string> &Checks,
+                           llvm::StringRef List) {
+  if (List.empty())
+    return;
+  if (!Checks || Checks->empty()) {
+    Checks.emplace(List);
+    return;
+  }
+  *Checks = llvm::join_items(",", *Checks, List);
+}
+
+static llvm::Optional<tidy::ClangTidyOptions>
+tryReadConfigFile(llvm::vfs::FileSystem *FS, llvm::StringRef Directory) {
+  assert(!Directory.empty());
+  // We guaranteed that child directories of Directory exist, so this assert
+  // should hopefully never fail.
+  assert(FS->exists(Directory));
+
+  llvm::SmallString<128> ConfigFile(Directory);
+  llvm::sys::path::append(ConfigFile, ".clang-tidy");
+
+  llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
+
+  if (!FileStatus || !FileStatus->isRegularFile())
+    return llvm::None;
+
+  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
+      FS->getBufferForFile(ConfigFile);
+  if (std::error_code EC = Text.getError()) {
+    elog("Can't read '{0}': {1}", ConfigFile, EC.message());
+    return llvm::None;
+  }
+
+  // Skip empty files, e.g. files opened for writing via shell output
+  // redirection.
+  if ((*Text)->getBuffer().empty())
+    return llvm::None;
+  llvm::ErrorOr<tidy::ClangTidyOptions> ParsedOptions =
+      tidy::parseConfiguration((*Text)->getBuffer());
+  if (!ParsedOptions) {
+    if (ParsedOptions.getError())
+      elog("Error parsing clang-tidy configuration in '{0}': {1}", ConfigFile,
+           ParsedOptions.getError().message());
+    return llvm::None;
+  }
+  return std::move(*ParsedOptions);
+}
+
+TidyProviderRef provideEnvironment() {
+  static const llvm::Optional<std::string> User = [] {
+    llvm::Optional<std::string> Ret = llvm::sys::Process::GetEnv("USER");
+#ifdef _WIN32
+    if (!Ret)
+      return llvm::sys::Process::GetEnv("USERNAME");
+#endif
+    return Ret;
+  }();
+
+  if (User)
+    return
+        [](tidy::ClangTidyOptions &Opts, llvm::StringRef) { Opts.User = User; };
+  // FIXME: Once function_ref and unique_function operator= operators handle
+  // null values, this can return null.
+  return [](tidy::ClangTidyOptions &, llvm::StringRef) {};
+}
+
+TidyProviderRef provideDefaultChecks() {
+  // These default checks are chosen for:
+  //  - low false-positive rate
+  //  - providing a lot of value
+  //  - being reasonably efficient
+  static const std::string DefaultChecks = llvm::join_items(
+      ",", "readability-misleading-indentation", "readability-deleted-default",
+      "bugprone-integer-division", "bugprone-sizeof-expression",
+      "bugprone-suspicious-missing-comma", "bugprone-unused-raii",
+      "bugprone-unused-return-value", "misc-unused-using-decls",
+      "misc-unused-alias-decls", "misc-definitions-in-headers");
+  return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
+    if (!Opts.Checks || Opts.Checks->empty())
+      Opts.Checks = DefaultChecks;
+  };
+}
+
+TidyProvider addTidyChecks(llvm::StringRef Checks,
+                           llvm::StringRef WarningsAsErrors) {
+  return [Checks = std::string(Checks),
+          WarningsAsErrors = std::string(WarningsAsErrors)](
+             tidy::ClangTidyOptions &Opts, llvm::StringRef) {
+    mergeCheckList(Opts.Checks, Checks);
+    mergeCheckList(Opts.WarningsAsErrors, WarningsAsErrors);
+  };
+}
+
+TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
+  constexpr llvm::StringLiteral Seperator(",");
+  static const std::string BadChecks =
+      llvm::join_items(Seperator,
+                       // We want this list to start with a seperator to
+                       // simplify appending in the lambda. So including an
+                       // empty string here will force that.
+                       "",
+                       // ----- False Positives -----
+
+                       // Check relies on seeing ifndef/define/endif directives,
+                       // clangd doesn't replay those when using a preamble.
+                       "-llvm-header-guard",
+
+                       // ----- Crashing Checks -----
+
+                       // Check can choke on invalid (intermediate) c++
+                       // code, which is often the case when clangd
+                       // tries to build an AST.
+                       "-bugprone-use-after-move");
+
+  size_t Size = BadChecks.size();
+  for (const std::string &Str : ExtraBadChecks) {
+    if (Str.empty())
+      continue;
+    Size += Seperator.size();
+    if (LLVM_LIKELY(Str.front() != '-'))
+      ++Size;
+    Size += Str.size();
+  }
+  std::string DisableGlob;
+  DisableGlob.reserve(Size);
+  DisableGlob += BadChecks;
+  for (const std::string &Str : ExtraBadChecks) {
+    if (Str.empty())
+      continue;
+    DisableGlob += Seperator;
+    if (LLVM_LIKELY(Str.front() != '-'))
+      DisableGlob.push_back('-');
+    DisableGlob += Str;
+  }
+
+  return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions &Opts,
+                                               llvm::StringRef) {
+    if (Opts.Checks && !Opts.Checks->empty())
+      Opts.Checks->append(DisableList);
+  };
+}
+
+TidyProviderRef provideClangdConfig() {
+  return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
+    const auto &CurTidyConfig = Config::current().ClangTidy;
+    if (!CurTidyConfig.Checks.empty())
+      mergeCheckList(Opts.Checks, CurTidyConfig.Checks);
+
+    for (const auto &CheckOption : CurTidyConfig.CheckOptions)
+      Opts.CheckOptions.insert_or_assign(CheckOption.getKey(),
+                                         tidy::ClangTidyOptions::ClangTidyValue(
+                                             CheckOption.getValue(), 10000U));
+  };
+}
+
+TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS) {
+  return [&TFS](tidy::ClangTidyOptions &Opts, llvm::StringRef Filename) {
+    llvm::SmallVector<tidy::ClangTidyOptions, 4> OptionStack;
+    auto FS(TFS.view(llvm::None));
+    llvm::SmallString<256> AbsolutePath(Filename);
+
+    assert(llvm::sys::path::is_absolute(AbsolutePath));
+
+    llvm::sys::path::remove_dots(AbsolutePath, true);
+    llvm::StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
+    {
+      auto Status = FS->status(Directory);
+
+      if (!Status || !Status->isDirectory()) {
+        elog("Error reading configuration from {0}: directory doesn't exist",
+             Directory);
+        return;
+      }
+    }
+
+    // FIXME: Store options in a cache that validates itself against changes
+    // during the clangd session.
+    for (llvm::StringRef CurrentDirectory = Directory;
+         !CurrentDirectory.empty();
+         CurrentDirectory = llvm::sys::path::parent_path(CurrentDirectory)) {
+      auto ConfigFile = tryReadConfigFile(FS.get(), CurrentDirectory);
+      if (!ConfigFile)
+        continue;
+      OptionStack.push_back(std::move(*ConfigFile));
+      // Should we search for a parent config to merge
+      if (!OptionStack.back().InheritParentConfig.getValueOr(false))
+        break;
+    }
+    unsigned Order = 1U;
+    for (auto &Option : llvm::reverse(OptionStack))
+      Opts.mergeWith(Option, Order++);
+  };
+}
+
+TidyProvider combine(std::vector<TidyProvider> Providers) {
+  // FIXME: Once function_ref and unique_function operator= operators handle
+  // null values, we should filter out any Providers that are null. Right now we
+  // have to ensure we dont pass any providers that are null.
+  return [Providers(std::move(Providers))](tidy::ClangTidyOptions &Opts,
+                                           llvm::StringRef Filename) {
+    for (const auto &Provider : Providers)
+      Provider(Opts, Filename);
+  };
+}
+
+tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider,
+                                             llvm::StringRef Filename) {
+  tidy::ClangTidyOptions Opts = tidy::ClangTidyOptions::getDefaults();
+  Opts.Checks->clear();
+  if (Provider)
+    Provider(Opts, Filename);
+  return Opts;
+}
+} // namespace clangd
+} // namespace clang

diff  --git a/clang-tools-extra/clangd/TidyProvider.h b/clang-tools-extra/clangd/TidyProvider.h
new file mode 100644
index 000000000000..f3f679c3f0d5
--- /dev/null
+++ b/clang-tools-extra/clangd/TidyProvider.h
@@ -0,0 +1,62 @@
+//===--- TidyProvider.h - create options for running clang-tidy------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TIDYPROVIDER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TIDYPROVIDER_H
+
+#include "../clang-tidy/ClangTidyOptions.h"
+#include "support/ThreadsafeFS.h"
+#include "llvm/ADT/FunctionExtras.h"
+#include "llvm/ADT/STLExtras.h"
+
+namespace clang {
+namespace clangd {
+
+/// A factory to modify a \ref tidy::ClangTidyOptions.
+using TidyProvider =
+    llvm::unique_function<void(tidy::ClangTidyOptions &,
+                               /*Filename=*/llvm::StringRef) const>;
+
+/// A factory to modify a \ref tidy::ClangTidyOptions that doesn't hold any
+/// state.
+using TidyProviderRef = llvm::function_ref<void(tidy::ClangTidyOptions &,
+                                                /*Filename=*/llvm::StringRef)>;
+
+TidyProvider combine(std::vector<TidyProvider> Providers);
+
+/// Provider that just sets the defaults.
+TidyProviderRef provideEnvironment();
+
+/// Provider that will enable a nice set of default checks if none are
+/// specified.
+TidyProviderRef provideDefaultChecks();
+
+/// Provider the enables a specific set of checks and warnings as errors.
+TidyProvider addTidyChecks(llvm::StringRef Checks,
+                           llvm::StringRef WarningsAsErrors = {});
+
+/// Provider that will disable checks known to not work with clangd. \p
+/// ExtraBadChecks specifies any other checks that should be always
+/// disabled.
+TidyProvider
+disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks = {});
+
+/// Provider that searches for .clang-tidy configuration files in the directory
+/// tree.
+TidyProvider provideClangTidyFiles(ThreadsafeFS &);
+
+// Provider that uses clangd configuration files.
+TidyProviderRef provideClangdConfig();
+
+tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider,
+                                             llvm::StringRef Filename);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_TIDYPROVIDER_H

diff  --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp
index 14ee0fdec9c9..9b40e840478e 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -133,8 +133,6 @@ class Checker {
         return false;
       }
     }
-    Inputs.Opts.ClangTidyOpts =
-        Opts.GetClangTidyOptions(*TFS.view(llvm::None), File);
     log("Parsing command...");
     Invocation =
         buildCompilerInvocation(Inputs, CaptureInvocationDiags, &CC1Args);

diff  --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 1a70058cd8ab..5c89e423cde4 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -11,6 +11,7 @@
 #include "Features.inc"
 #include "PathMapping.h"
 #include "Protocol.h"
+#include "TidyProvider.h"
 #include "Transport.h"
 #include "index/Background.h"
 #include "index/Index.h"
@@ -837,35 +838,21 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
   }
 
   // Create an empty clang-tidy option.
-  std::mutex ClangTidyOptMu;
-  std::unique_ptr<tidy::ClangTidyOptionsProvider>
-      ClangTidyOptProvider; /*GUARDED_BY(ClangTidyOptMu)*/
+  TidyProvider ClangTidyOptProvider;
   if (EnableClangTidy) {
-    auto EmptyDefaults = tidy::ClangTidyOptions::getDefaults();
-    EmptyDefaults.Checks.reset(); // So we can tell if checks were ever set.
-    EmptyDefaults.User = llvm::sys::Process::GetEnv("USER");
-#ifdef _WIN32
-    if (!EmptyDefaults.User)
-      EmptyDefaults.User = llvm::sys::Process::GetEnv("USERNAME");
-#endif
-    tidy::ClangTidyOptions OverrideClangTidyOptions;
+    std::vector<TidyProvider> Providers;
+    Providers.reserve(4 + EnableConfig);
+    Providers.push_back(provideEnvironment());
+    Providers.push_back(provideClangTidyFiles(TFS));
+    if (EnableConfig)
+      Providers.push_back(provideClangdConfig());
     if (!ClangTidyChecks.empty())
-      OverrideClangTidyOptions.Checks = ClangTidyChecks;
-    ClangTidyOptProvider = std::make_unique<tidy::FileOptionsProvider>(
-        tidy::ClangTidyGlobalOptions(),
-        /* Default */ EmptyDefaults,
-        /* Override */ OverrideClangTidyOptions, TFS.view(/*CWD=*/llvm::None));
-    Opts.GetClangTidyOptions = [&](llvm::vfs::FileSystem &,
-                                   llvm::StringRef File) {
-      // This function must be thread-safe and tidy option providers are not.
-      tidy::ClangTidyOptions Opts;
-      {
-        std::lock_guard<std::mutex> Lock(ClangTidyOptMu);
-        // FIXME: use the FS provided to the function.
-        Opts = ClangTidyOptProvider->getOptions(File);
-      }
-      return Opts;
-    };
+      Providers.push_back(addTidyChecks(ClangTidyChecks));
+    else
+      Providers.push_back(provideDefaultChecks());
+    Providers.push_back(disableUnusableChecks());
+    ClangTidyOptProvider = combine(std::move(Providers));
+    Opts.ClangTidyProvider = ClangTidyOptProvider;
   }
   Opts.AsyncPreambleBuilds = AsyncPreamble;
   Opts.SuggestMissingIncludes = SuggestMissingIncludes;

diff  --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp
index f2d6d6b8192b..06d761d4fabe 100644
--- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp
@@ -16,6 +16,7 @@
 #include "SyncAPI.h"
 #include "TestFS.h"
 #include "TestTU.h"
+#include "TidyProvider.h"
 #include "URI.h"
 #include "support/MemoryTree.h"
 #include "support/Path.h"
@@ -1215,16 +1216,19 @@ TEST(ClangdServer, TidyOverrideTest) {
   } DiagConsumer;
 
   MockFS FS;
+  // These checks don't work well in clangd, even if configured they shouldn't
+  // run.
+  FS.Files[testPath(".clang-tidy")] = R"(
+    Checks: -*,bugprone-use-after-move,llvm-header-guard
+  )";
   MockCompilationDatabase CDB;
+  std::vector<TidyProvider> Stack;
+  Stack.push_back(provideClangTidyFiles(FS));
+  Stack.push_back(disableUnusableChecks());
+  TidyProvider Provider = combine(std::move(Stack));
   CDB.ExtraClangFlags = {"-xc++"};
   auto Opts = ClangdServer::optsForTest();
-  Opts.GetClangTidyOptions = [](llvm::vfs::FileSystem &, llvm::StringRef) {
-    auto Opts = tidy::ClangTidyOptions::getDefaults();
-    // These checks don't work well in clangd, even if configured they shouldn't
-    // run.
-    Opts.Checks = "bugprone-use-after-move,llvm-header-guard";
-    return Opts;
-  };
+  Opts.ClangTidyProvider = Provider;
   ClangdServer Server(CDB, FS, Opts, &DiagConsumer);
   const char *SourceContents = R"cpp(
     struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); };

diff  --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
index 8f84d0ba4806..ba7029e54dbb 100644
--- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -14,6 +14,7 @@
 #include "TestFS.h"
 #include "TestIndex.h"
 #include "TestTU.h"
+#include "TidyProvider.h"
 #include "index/MemIndex.h"
 #include "support/Path.h"
 #include "clang/Basic/Diagnostic.h"
@@ -129,7 +130,7 @@ o]]();
     }
   )cpp");
   auto TU = TestTU::withCode(Test.code());
-  TU.ClangTidyChecks = "-*,google-explicit-constructor";
+  TU.ClangTidyProvider = addTidyChecks("google-explicit-constructor");
   EXPECT_THAT(
       TU.build().getDiagnostics(),
       ElementsAre(
@@ -201,8 +202,8 @@ TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
   auto TU = TestTU::withCode(Test.code());
   // Enable alias clang-tidy checks, these check emits the same diagnostics
   // (except the check name).
-  TU.ClangTidyChecks = "-*, readability-uppercase-literal-suffix, "
-                       "hicpp-uppercase-literal-suffix";
+  TU.ClangTidyProvider = addTidyChecks("readability-uppercase-literal-suffix,"
+                                       "hicpp-uppercase-literal-suffix");
   // Verify that we filter out the duplicated diagnostic message.
   EXPECT_THAT(
       TU.build().getDiagnostics(),
@@ -245,9 +246,10 @@ TEST(DiagnosticsTest, ClangTidy) {
   )cpp");
   auto TU = TestTU::withCode(Test.code());
   TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
-  TU.ClangTidyChecks =
-      "-*, bugprone-sizeof-expression, bugprone-macro-repeated-side-effects, "
-      "modernize-deprecated-headers, modernize-use-trailing-return-type";
+  TU.ClangTidyProvider = addTidyChecks("bugprone-sizeof-expression,"
+                                       "bugprone-macro-repeated-side-effects,"
+                                       "modernize-deprecated-headers,"
+                                       "modernize-use-trailing-return-type");
   EXPECT_THAT(
       TU.build().getDiagnostics(),
       UnorderedElementsAre(
@@ -289,7 +291,7 @@ TEST(DiagnosticsTest, ClangTidyEOF) {
   auto TU = TestTU::withCode(Test.code());
   TU.ExtraArgs = {"-isystem."};
   TU.AdditionalFiles["a.h"] = TU.AdditionalFiles["b.h"] = "";
-  TU.ClangTidyChecks = "-*, llvm-include-order";
+  TU.ClangTidyProvider = addTidyChecks("llvm-include-order");
   EXPECT_THAT(
       TU.build().getDiagnostics(),
       Contains(AllOf(Diag(Test.range(), "#includes are not sorted properly"),
@@ -361,7 +363,7 @@ TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {
     }
   )cpp");
   TestTU TU = TestTU::withCode(Main.code());
-  TU.ClangTidyChecks = "modernize-loop-convert";
+  TU.ClangTidyProvider = addTidyChecks("modernize-loop-convert");
   EXPECT_THAT(
       TU.build().getDiagnostics(),
       UnorderedElementsAre(::testing::AllOf(
@@ -384,7 +386,7 @@ TEST(DiagnosticTest, ClangTidySuppressionComment) {
     }
   )cpp");
   TestTU TU = TestTU::withCode(Main.code());
-  TU.ClangTidyChecks = "bugprone-integer-division";
+  TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");
   EXPECT_THAT(
       TU.build().getDiagnostics(),
       UnorderedElementsAre(::testing::AllOf(
@@ -401,8 +403,8 @@ TEST(DiagnosticTest, ClangTidyWarningAsError) {
     }
   )cpp");
   TestTU TU = TestTU::withCode(Main.code());
-  TU.ClangTidyChecks = "bugprone-integer-division";
-  TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
+  TU.ClangTidyProvider =
+      addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
   EXPECT_THAT(
       TU.build().getDiagnostics(),
       UnorderedElementsAre(::testing::AllOf(
@@ -452,8 +454,8 @@ TEST(DiagnosticTest, ClangTidySuppressionCommentTrumpsWarningAsError) {
     }
   )cpp");
   TestTU TU = TestTU::withCode(Main.code());
-  TU.ClangTidyChecks = "bugprone-integer-division";
-  TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
+  TU.ClangTidyProvider =
+      addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
   EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
 }
 
@@ -468,7 +470,7 @@ TEST(DiagnosticTest, ClangTidyNoLiteralDataInMacroToken) {
     }
   )cpp");
   TestTU TU = TestTU::withCode(Main.code());
-  TU.ClangTidyChecks = "-*,bugprone-bad-signal-to-kill-thread";
+  TU.ClangTidyProvider = addTidyChecks("bugprone-bad-signal-to-kill-thread");
   EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash
 }
 

diff  --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
index 3fd7ded2b356..b3321f5a6479 100644
--- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
@@ -24,6 +24,7 @@
 #include "SourceCode.h"
 #include "TestFS.h"
 #include "TestTU.h"
+#include "TidyProvider.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
@@ -250,7 +251,7 @@ TEST(ParsedASTTest, NoCrashOnTokensWithTidyCheck) {
   TestTU TU;
   // this check runs the preprocessor, we need to make sure it does not break
   // our recording logic.
-  TU.ClangTidyChecks = "modernize-use-trailing-return-type";
+  TU.ClangTidyProvider = addTidyChecks("modernize-use-trailing-return-type");
   TU.Code = "inline int foo() {}";
 
   auto AST = TU.build();
@@ -406,7 +407,7 @@ TEST(ParsedASTTest, ReplayPreambleForTidyCheckers) {
       "replay-preamble-module", "");
   TestTU TU;
   // This check records inclusion directives replayed by clangd.
-  TU.ClangTidyChecks = "replay-preamble-check";
+  TU.ClangTidyProvider = addTidyChecks("replay-preamble-check");
   llvm::Annotations Test(R"cpp(
     $hash^#$include[[import]] $filebegin^"$filerange[[bar.h]]"
     $hash^#$include[[include_next]] $filebegin^"$filerange[[baz.h]]"

diff  --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp
index ad0501c1d6a3..f487cf717f04 100644
--- a/clang-tools-extra/clangd/unittests/TestTU.cpp
+++ b/clang-tools-extra/clangd/unittests/TestTU.cpp
@@ -59,8 +59,8 @@ ParseInputs TestTU::inputs(MockFS &FS) const {
     FS.OverlayRealFileSystemForModules = true;
   Inputs.TFS = &FS;
   Inputs.Opts = ParseOptions();
-  Inputs.Opts.ClangTidyOpts.Checks = ClangTidyChecks;
-  Inputs.Opts.ClangTidyOpts.WarningsAsErrors = ClangTidyWarningsAsErrors;
+  if (ClangTidyProvider)
+    Inputs.ClangTidyProvider = ClangTidyProvider;
   Inputs.Index = ExternalIndex;
   if (Inputs.Index)
     Inputs.Opts.SuggestMissingIncludes = true;

diff  --git a/clang-tools-extra/clangd/unittests/TestTU.h b/clang-tools-extra/clangd/unittests/TestTU.h
index f383e693408c..18b490332b1a 100644
--- a/clang-tools-extra/clangd/unittests/TestTU.h
+++ b/clang-tools-extra/clangd/unittests/TestTU.h
@@ -17,6 +17,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTTU_H
 #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTTU_H
 
+#include "../TidyProvider.h"
 #include "Compiler.h"
 #include "ParsedAST.h"
 #include "TestFS.h"
@@ -58,8 +59,7 @@ struct TestTU {
   // Extra arguments for the compiler invocation.
   std::vector<std::string> ExtraArgs;
 
-  llvm::Optional<std::string> ClangTidyChecks;
-  llvm::Optional<std::string> ClangTidyWarningsAsErrors;
+  TidyProvider ClangTidyProvider = {};
   // Index to use when building AST.
   const SymbolIndex *ExternalIndex = nullptr;
 


        


More information about the cfe-commits mailing list