From 4f6b06673d5421f8d4e5299b7c7cd62103c09ac0 Mon Sep 17 00:00:00 2001 From: Alexander Kornienko Date: Mon, 20 Oct 2014 12:29:15 +0000 Subject: [PATCH] [clang-tidy] Add support for custom configuration file names/formats. Summary: We're using different clang-tidy frontends (command-line, batch analysis jobs, code review integration), some of which are limited in the choice of configuration format. In order to avoid duplication of configuration information, we need to support the same configuration format in the command-line tool. This patch adds an extension point to make this possible without rewriting FileOptionsProvider. Reviewers: djasper Reviewed By: djasper Subscribers: cfe-commits Differential Revision: http://reviews.llvm.org/D5821 llvm-svn: 220199 --- clang-tools-extra/clang-tidy/ClangTidyOptions.cpp | 85 ++++++++++++++--------- clang-tools-extra/clang-tidy/ClangTidyOptions.h | 66 ++++++++++++++++-- 2 files changed, 111 insertions(+), 40 deletions(-) diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp index 5283a5c..6a3aaeb 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp @@ -139,10 +139,19 @@ FileOptionsProvider::FileOptionsProvider( const ClangTidyOptions &OverrideOptions) : DefaultOptionsProvider(GlobalOptions, DefaultOptions), OverrideOptions(OverrideOptions) { + ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions); } -static const char ConfigFileName[] = ".clang-tidy"; +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) { + CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions); +} // FIXME: This method has some common logic with clang::format::getStyle(). // Consider pulling out common bits to a findParentFileWithName function or @@ -164,7 +173,7 @@ const ClangTidyOptions &FileOptionsProvider::getOptions(StringRef FileName) { StringRef Path = llvm::sys::path::parent_path(FileName); for (StringRef CurrentPath = Path;; CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { - llvm::ErrorOr Result = std::error_code(); + llvm::Optional Result; auto Iter = CachedOptions.find(CurrentPath); if (Iter != CachedOptions.end()) @@ -183,49 +192,57 @@ const ClangTidyOptions &FileOptionsProvider::getOptions(StringRef FileName) { } return CachedOptions.GetOrCreateValue(Path, *Result).getValue(); } - if (Result.getError() != llvm::errc::no_such_file_or_directory) { - llvm::errs() << "Error reading " << ConfigFileName << " from " << Path - << ": " << Result.getError().message() << "\n"; - } } } -llvm::ErrorOr +llvm::Optional FileOptionsProvider::TryReadConfigFile(StringRef Directory) { assert(!Directory.empty()); - if (!llvm::sys::fs::is_directory(Directory)) - return make_error_code(llvm::errc::not_a_directory); - - SmallString<128> ConfigFile(Directory); - llvm::sys::path::append(ConfigFile, ".clang-tidy"); - DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); - - bool IsFile = false; - // Ignore errors from is_regular_file: we only need to know if we can read - // the file or not. - llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile); - - if (!IsFile) - return make_error_code(llvm::errc::no_such_file_or_directory); - - llvm::ErrorOr> Text = - llvm::MemoryBuffer::getFile(ConfigFile.c_str()); - if (std::error_code EC = Text.getError()) - return EC; - // Skip empty files, e.g. files opened for writing via shell output - // redirection. - if ((*Text)->getBuffer().empty()) - return make_error_code(llvm::errc::no_such_file_or_directory); - llvm::ErrorOr ParsedOptions = - parseConfiguration((*Text)->getBuffer()); - if (ParsedOptions) { + if (!llvm::sys::fs::is_directory(Directory)) { + llvm::errs() << "Error reading configuration from " << Directory + << ": directory doesn't exist.\n"; + return llvm::None; + } + + for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) { + SmallString<128> ConfigFile(Directory); + llvm::sys::path::append(ConfigFile, ConfigHandler.first); + DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); + + bool IsFile = false; + // Ignore errors from is_regular_file: we only need to know if we can read + // the file or not. + llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile); + if (!IsFile) + continue; + + llvm::ErrorOr> Text = + llvm::MemoryBuffer::getFile(ConfigFile.c_str()); + if (std::error_code EC = Text.getError()) { + llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message() + << "\n"; + continue; + } + + // Skip empty files, e.g. files opened for writing via shell output + // redirection. + if ((*Text)->getBuffer().empty()) + continue; + llvm::ErrorOr ParsedOptions = + ConfigHandler.second((*Text)->getBuffer()); + if (!ParsedOptions) { + llvm::errs() << "Error parsing " << ConfigFile << ": " + << ParsedOptions.getError().message() << "\n"; + continue; + } + ClangTidyOptions Defaults = DefaultOptionsProvider::getOptions(Directory); // Only use checks from the config file. Defaults.Checks = None; return Defaults.mergeWith(*ParsedOptions).mergeWith(OverrideOptions); } - return ParsedOptions.getError(); + return llvm::None; } /// \brief Parses -line-filter option and stores it to the \c Options. diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h index c45baf3..8a89c59 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h @@ -14,6 +14,7 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ErrorOr.h" +#include #include #include #include @@ -114,32 +115,85 @@ private: }; /// \brief Implementation of the \c ClangTidyOptionsProvider interface, which -/// tries to find a .clang-tidy file in the closest parent directory of each -/// file. +/// tries to find a configuration file in the closest parent directory of each +/// source file. +/// +/// By default, files named ".clang-tidy" will be considered, and the +/// \c clang::tidy::parseConfiguration function will be used for parsing, but a +/// custom set of configuration file names and parsing functions can be +/// specified using the appropriate constructor. class FileOptionsProvider : public DefaultOptionsProvider { public: + // \brief A pair of configuration file base name and a function parsing + // configuration from text in the corresponding format. + typedef std::pair( + llvm::StringRef)>> ConfigFileHandler; + + /// \brief Configuration file handlers listed in the order of priority. + /// + /// Custom configuration file formats can be supported by constructing the + /// list of handlers and passing it to the appropriate \c FileOptionsProvider + /// constructor. E.g. initialization of a \c FileOptionsProvider with support + /// of a custom configuration file format for files named ".my-tidy-config" + /// could look similar to this: + /// \code + /// FileOptionsProvider::ConfigFileHandlers ConfigHandlers; + /// ConfigHandlers.emplace_back(".my-tidy-config", parseMyConfigFormat); + /// ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); + /// return llvm::make_unique( + /// GlobalOptions, DefaultOptions, OverrideOptions, ConfigHandlers); + /// \endcode + /// + /// With the order of handlers shown above, the ".my-tidy-config" file would + /// take precedence over ".clang-tidy" if both reside in the same directory. + typedef std::vector ConfigFileHandlers; + /// \brief Initializes the \c FileOptionsProvider instance. /// /// \param GlobalOptions are just stored and returned to the caller of /// \c getGlobalOptions. /// /// \param DefaultOptions are used for all settings not specified in a - /// .clang-tidy file. + /// configuration file. /// /// If any of the \param OverrideOptions fields are set, they will override /// whatever options are read from the configuration file. FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, const ClangTidyOptions &OverrideOptions); + + /// \brief Initializes the \c FileOptionsProvider instance with a custom set + /// of configuration file handlers. + /// + /// \param GlobalOptions are just stored and returned to the caller of + /// \c getGlobalOptions. + /// + /// \param DefaultOptions are used for all settings not specified in a + /// configuration file. + /// + /// If any of the \param OverrideOptions fields are set, they will override + /// whatever options are read from the configuration file. + /// + /// \param ConfigHandlers specifies a custom set of configuration file + /// handlers. Each handler is a pair of configuration file name and a function + /// that can parse configuration from this file type. The configuration files + /// in each directory are searched for in the order of appearance in + /// \p ConfigHandlers. + FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const ConfigFileHandlers &ConfigHandlers); + const ClangTidyOptions &getOptions(llvm::StringRef FileName) override; private: - /// \brief Try to read configuration file from \p Directory. If \p Directory - /// is empty, use the default value. - llvm::ErrorOr TryReadConfigFile(llvm::StringRef Directory); + /// \brief Try to read configuration files from \p Directory using registered + /// \c ConfigHandlers. + llvm::Optional TryReadConfigFile(llvm::StringRef Directory); llvm::StringMap CachedOptions; ClangTidyOptions OverrideOptions; + ConfigFileHandlers ConfigHandlers; }; /// \brief Parses LineFilter from JSON and stores it to the \p Options. -- 2.7.4