Index: clang-tidy/ClangTidyOptions.h =================================================================== --- clang-tidy/ClangTidyOptions.h +++ 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 @@ }; /// \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. Index: clang-tidy/ClangTidyOptions.cpp =================================================================== --- clang-tidy/ClangTidyOptions.cpp +++ clang-tidy/ClangTidyOptions.cpp @@ -139,10 +139,19 @@ 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,14 +173,14 @@ 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()) Result = Iter->second; if (!Result) - Result = TryReadConfigFile(CurrentPath); + Result = TryReadConfigFile(CurrentPath); if (Result) { // Store cached value for all intermediate directories. @@ -183,49 +192,57 @@ } 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); + 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; + } - 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) { 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.