|  | //===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "ClangTidyOptions.h" | 
|  | #include "ClangTidyModuleRegistry.h" | 
|  | #include "clang/Basic/LLVM.h" | 
|  | #include "llvm/ADT/SmallString.h" | 
|  | #include "llvm/Support/Debug.h" | 
|  | #include "llvm/Support/Errc.h" | 
|  | #include "llvm/Support/FileSystem.h" | 
|  | #include "llvm/Support/Path.h" | 
|  | #include "llvm/Support/YAMLTraits.h" | 
|  | #include "llvm/Support/raw_ostream.h" | 
|  | #include <utility> | 
|  |  | 
|  | #define DEBUG_TYPE "clang-tidy-options" | 
|  |  | 
|  | using clang::tidy::ClangTidyOptions; | 
|  | using clang::tidy::FileFilter; | 
|  | using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource; | 
|  |  | 
|  | LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter) | 
|  | LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange) | 
|  |  | 
|  | namespace llvm { | 
|  | namespace yaml { | 
|  |  | 
|  | // Map std::pair<int, int> to a JSON array of size 2. | 
|  | template <> struct SequenceTraits<FileFilter::LineRange> { | 
|  | static size_t size(IO &IO, FileFilter::LineRange &Range) { | 
|  | return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2; | 
|  | } | 
|  | static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) { | 
|  | if (Index > 1) | 
|  | IO.setError("Too many elements in line range."); | 
|  | return Index == 0 ? Range.first : Range.second; | 
|  | } | 
|  | }; | 
|  |  | 
|  | template <> struct MappingTraits<FileFilter> { | 
|  | static void mapping(IO &IO, FileFilter &File) { | 
|  | IO.mapRequired("name", File.Name); | 
|  | IO.mapOptional("lines", File.LineRanges); | 
|  | } | 
|  | static StringRef validate(IO &io, FileFilter &File) { | 
|  | if (File.Name.empty()) | 
|  | return "No file name specified"; | 
|  | for (const FileFilter::LineRange &Range : File.LineRanges) { | 
|  | if (Range.first <= 0 || Range.second <= 0) | 
|  | return "Invalid line range"; | 
|  | } | 
|  | return StringRef(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | template <> struct MappingTraits<ClangTidyOptions::StringPair> { | 
|  | static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) { | 
|  | IO.mapRequired("key", KeyValue.first); | 
|  | IO.mapRequired("value", KeyValue.second); | 
|  | } | 
|  | }; | 
|  |  | 
|  | struct NOptionMap { | 
|  | NOptionMap(IO &) {} | 
|  | NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) | 
|  | : Options(OptionMap.begin(), OptionMap.end()) {} | 
|  | ClangTidyOptions::OptionMap denormalize(IO &) { | 
|  | ClangTidyOptions::OptionMap Map; | 
|  | for (const auto &KeyValue : Options) | 
|  | Map[KeyValue.first] = KeyValue.second; | 
|  | return Map; | 
|  | } | 
|  | std::vector<ClangTidyOptions::StringPair> Options; | 
|  | }; | 
|  |  | 
|  | template <> struct MappingTraits<ClangTidyOptions> { | 
|  | static void mapping(IO &IO, ClangTidyOptions &Options) { | 
|  | MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts( | 
|  | IO, Options.CheckOptions); | 
|  | IO.mapOptional("Checks", Options.Checks); | 
|  | IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors); | 
|  | IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); | 
|  | IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors); | 
|  | IO.mapOptional("FormatStyle", Options.FormatStyle); | 
|  | IO.mapOptional("User", Options.User); | 
|  | IO.mapOptional("CheckOptions", NOpts->Options); | 
|  | IO.mapOptional("ExtraArgs", Options.ExtraArgs); | 
|  | IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore); | 
|  | } | 
|  | }; | 
|  |  | 
|  | } // namespace yaml | 
|  | } // namespace llvm | 
|  |  | 
|  | namespace clang { | 
|  | namespace tidy { | 
|  |  | 
|  | ClangTidyOptions ClangTidyOptions::getDefaults() { | 
|  | ClangTidyOptions Options; | 
|  | Options.Checks = ""; | 
|  | Options.WarningsAsErrors = ""; | 
|  | Options.HeaderFilterRegex = ""; | 
|  | Options.SystemHeaders = false; | 
|  | Options.AnalyzeTemporaryDtors = false; | 
|  | Options.FormatStyle = "none"; | 
|  | Options.User = llvm::None; | 
|  | for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), | 
|  | E = ClangTidyModuleRegistry::end(); | 
|  | I != E; ++I) | 
|  | Options = Options.mergeWith(I->instantiate()->getModuleOptions()); | 
|  | return Options; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static void mergeVectors(Optional<T> &Dest, const Optional<T> &Src) { | 
|  | if (Src) { | 
|  | if (Dest) | 
|  | Dest->insert(Dest->end(), Src->begin(), Src->end()); | 
|  | else | 
|  | Dest = Src; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void mergeCommaSeparatedLists(Optional<std::string> &Dest, | 
|  | const Optional<std::string> &Src) { | 
|  | if (Src) | 
|  | Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static void overrideValue(Optional<T> &Dest, const Optional<T> &Src) { | 
|  | if (Src) | 
|  | Dest = Src; | 
|  | } | 
|  |  | 
|  | ClangTidyOptions | 
|  | ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const { | 
|  | ClangTidyOptions Result = *this; | 
|  |  | 
|  | mergeCommaSeparatedLists(Result.Checks, Other.Checks); | 
|  | mergeCommaSeparatedLists(Result.WarningsAsErrors, Other.WarningsAsErrors); | 
|  | overrideValue(Result.HeaderFilterRegex, Other.HeaderFilterRegex); | 
|  | overrideValue(Result.SystemHeaders, Other.SystemHeaders); | 
|  | overrideValue(Result.AnalyzeTemporaryDtors, Other.AnalyzeTemporaryDtors); | 
|  | overrideValue(Result.FormatStyle, Other.FormatStyle); | 
|  | overrideValue(Result.User, Other.User); | 
|  | mergeVectors(Result.ExtraArgs, Other.ExtraArgs); | 
|  | mergeVectors(Result.ExtraArgsBefore, Other.ExtraArgsBefore); | 
|  |  | 
|  | for (const auto &KeyValue : Other.CheckOptions) | 
|  | Result.CheckOptions[KeyValue.first] = KeyValue.second; | 
|  |  | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] = | 
|  | "clang-tidy binary"; | 
|  | const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] = | 
|  | "command-line option '-checks'"; | 
|  | const char | 
|  | ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] = | 
|  | "command-line option '-config'"; | 
|  |  | 
|  | ClangTidyOptions | 
|  | ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) { | 
|  | ClangTidyOptions Result; | 
|  | for (const auto &Source : getRawOptions(FileName)) | 
|  | Result = Result.mergeWith(Source.first); | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | std::vector<OptionsSource> | 
|  | DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) { | 
|  | std::vector<OptionsSource> Result; | 
|  | Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary); | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | ConfigOptionsProvider::ConfigOptionsProvider( | 
|  | const ClangTidyGlobalOptions &GlobalOptions, | 
|  | const ClangTidyOptions &DefaultOptions, | 
|  | const ClangTidyOptions &ConfigOptions, | 
|  | const ClangTidyOptions &OverrideOptions) | 
|  | : DefaultOptionsProvider(GlobalOptions, DefaultOptions), | 
|  | ConfigOptions(ConfigOptions), OverrideOptions(OverrideOptions) {} | 
|  |  | 
|  | std::vector<OptionsSource> | 
|  | ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) { | 
|  | std::vector<OptionsSource> RawOptions = | 
|  | DefaultOptionsProvider::getRawOptions(FileName); | 
|  | RawOptions.emplace_back(ConfigOptions, | 
|  | OptionsSourceTypeConfigCommandLineOption); | 
|  | RawOptions.emplace_back(OverrideOptions, | 
|  | OptionsSourceTypeCheckCommandLineOption); | 
|  | return RawOptions; | 
|  | } | 
|  |  | 
|  | FileOptionsProvider::FileOptionsProvider( | 
|  | const ClangTidyGlobalOptions &GlobalOptions, | 
|  | const ClangTidyOptions &DefaultOptions, | 
|  | const ClangTidyOptions &OverrideOptions) | 
|  | : DefaultOptionsProvider(GlobalOptions, DefaultOptions), | 
|  | OverrideOptions(OverrideOptions) { | 
|  | ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); | 
|  | } | 
|  |  | 
|  | FileOptionsProvider::FileOptionsProvider( | 
|  | const ClangTidyGlobalOptions &GlobalOptions, | 
|  | const ClangTidyOptions &DefaultOptions, | 
|  | const ClangTidyOptions &OverrideOptions, | 
|  | const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers) | 
|  | : DefaultOptionsProvider(GlobalOptions, DefaultOptions), | 
|  | OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {} | 
|  |  | 
|  | // FIXME: This method has some common logic with clang::format::getStyle(). | 
|  | // Consider pulling out common bits to a findParentFileWithName function or | 
|  | // similar. | 
|  | std::vector<OptionsSource> | 
|  | FileOptionsProvider::getRawOptions(StringRef FileName) { | 
|  | DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); | 
|  |  | 
|  | std::vector<OptionsSource> RawOptions = | 
|  | DefaultOptionsProvider::getRawOptions(FileName); | 
|  | OptionsSource CommandLineOptions(OverrideOptions, | 
|  | OptionsSourceTypeCheckCommandLineOption); | 
|  | // Look for a suitable configuration file in all parent directories of the | 
|  | // file. Start with the immediate parent directory and move up. | 
|  | StringRef Path = llvm::sys::path::parent_path(FileName); | 
|  | for (StringRef CurrentPath = Path; !CurrentPath.empty(); | 
|  | CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { | 
|  | llvm::Optional<OptionsSource> Result; | 
|  |  | 
|  | auto Iter = CachedOptions.find(CurrentPath); | 
|  | if (Iter != CachedOptions.end()) | 
|  | Result = Iter->second; | 
|  |  | 
|  | if (!Result) | 
|  | Result = tryReadConfigFile(CurrentPath); | 
|  |  | 
|  | if (Result) { | 
|  | // Store cached value for all intermediate directories. | 
|  | while (Path != CurrentPath) { | 
|  | DEBUG(llvm::dbgs() << "Caching configuration for path " << Path | 
|  | << ".\n"); | 
|  | CachedOptions[Path] = *Result; | 
|  | Path = llvm::sys::path::parent_path(Path); | 
|  | } | 
|  | CachedOptions[Path] = *Result; | 
|  |  | 
|  | RawOptions.push_back(*Result); | 
|  | break; | 
|  | } | 
|  | } | 
|  | RawOptions.push_back(CommandLineOptions); | 
|  | return RawOptions; | 
|  | } | 
|  |  | 
|  | llvm::Optional<OptionsSource> | 
|  | FileOptionsProvider::tryReadConfigFile(StringRef Directory) { | 
|  | assert(!Directory.empty()); | 
|  |  | 
|  | 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<std::unique_ptr<llvm::MemoryBuffer>> 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<ClangTidyOptions> ParsedOptions = | 
|  | ConfigHandler.second((*Text)->getBuffer()); | 
|  | if (!ParsedOptions) { | 
|  | if (ParsedOptions.getError()) | 
|  | llvm::errs() << "Error parsing " << ConfigFile << ": " | 
|  | << ParsedOptions.getError().message() << "\n"; | 
|  | continue; | 
|  | } | 
|  | return OptionsSource(*ParsedOptions, ConfigFile.c_str()); | 
|  | } | 
|  | return llvm::None; | 
|  | } | 
|  |  | 
|  | /// \brief Parses -line-filter option and stores it to the \c Options. | 
|  | std::error_code parseLineFilter(StringRef LineFilter, | 
|  | clang::tidy::ClangTidyGlobalOptions &Options) { | 
|  | llvm::yaml::Input Input(LineFilter); | 
|  | Input >> Options.LineFilter; | 
|  | return Input.error(); | 
|  | } | 
|  |  | 
|  | llvm::ErrorOr<ClangTidyOptions> parseConfiguration(StringRef Config) { | 
|  | llvm::yaml::Input Input(Config); | 
|  | ClangTidyOptions Options; | 
|  | Input >> Options; | 
|  | if (Input.error()) | 
|  | return Input.error(); | 
|  | return Options; | 
|  | } | 
|  |  | 
|  | std::string configurationAsText(const ClangTidyOptions &Options) { | 
|  | std::string Text; | 
|  | llvm::raw_string_ostream Stream(Text); | 
|  | llvm::yaml::Output Output(Stream); | 
|  | // We use the same mapping method for input and output, so we need a non-const | 
|  | // reference here. | 
|  | ClangTidyOptions NonConstValue = Options; | 
|  | Output << NonConstValue; | 
|  | return Stream.str(); | 
|  | } | 
|  |  | 
|  | } // namespace tidy | 
|  | } // namespace clang |