//===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===// // // 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 "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 #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 to a JSON array of size 2. template <> struct SequenceTraits { 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 { static void mapping(IO &IO, FileFilter &File) { IO.mapRequired("name", File.Name); IO.mapOptional("lines", File.LineRanges); } static std::string 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 ""; } }; template <> struct MappingTraits { 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.reserve(OptionMap.size()); for (const auto &KeyValue : OptionMap) Options.emplace_back(std::string(KeyValue.getKey()), KeyValue.getValue().Value); } ClangTidyOptions::OptionMap denormalize(IO &) { ClangTidyOptions::OptionMap Map; for (const auto &KeyValue : Options) Map[KeyValue.first] = ClangTidyOptions::ClangTidyValue(KeyValue.second); return Map; } std::vector Options; }; template <> struct MappingTraits { static void mapping(IO &IO, ClangTidyOptions &Options) { MappingNormalization NOpts( IO, Options.CheckOptions); bool Ignored = false; IO.mapOptional("Checks", Options.Checks); IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors); IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // legacy compatibility 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); IO.mapOptional("InheritParentConfig", Options.InheritParentConfig); IO.mapOptional("UseColor", Options.UseColor); } }; } // namespace yaml } // namespace llvm namespace clang { namespace tidy { ClangTidyOptions ClangTidyOptions::getDefaults() { ClangTidyOptions Options; Options.Checks = ""; Options.WarningsAsErrors = ""; Options.HeaderFilterRegex = ""; Options.SystemHeaders = false; Options.FormatStyle = "none"; Options.User = llvm::None; for (const ClangTidyModuleRegistry::entry &Module : ClangTidyModuleRegistry::entries()) Options.mergeWith(Module.instantiate()->getModuleOptions(), 0); return Options; } template static void mergeVectors(Optional &Dest, const Optional &Src) { if (Src) { if (Dest) Dest->insert(Dest->end(), Src->begin(), Src->end()); else Dest = Src; } } static void mergeCommaSeparatedLists(Optional &Dest, const Optional &Src) { if (Src) Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src; } template static void overrideValue(Optional &Dest, const Optional &Src) { if (Src) Dest = Src; } ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other, unsigned Order) { mergeCommaSeparatedLists(Checks, Other.Checks); mergeCommaSeparatedLists(WarningsAsErrors, Other.WarningsAsErrors); overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex); overrideValue(SystemHeaders, Other.SystemHeaders); overrideValue(FormatStyle, Other.FormatStyle); overrideValue(User, Other.User); overrideValue(UseColor, Other.UseColor); mergeVectors(ExtraArgs, Other.ExtraArgs); mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore); for (const auto &KeyValue : Other.CheckOptions) { CheckOptions.insert_or_assign( KeyValue.getKey(), ClangTidyValue(KeyValue.getValue().Value, KeyValue.getValue().Priority + Order)); } return *this; } ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other, unsigned Order) const { ClangTidyOptions Result = *this; Result.mergeWith(Other, Order); 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; unsigned Priority = 0; for (auto &Source : getRawOptions(FileName)) Result.mergeWith(Source.first, ++Priority); return Result; } std::vector DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) { std::vector Result; Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary); return Result; } ConfigOptionsProvider::ConfigOptionsProvider( const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, const ClangTidyOptions &ConfigOptions, const ClangTidyOptions &OverrideOptions, llvm::IntrusiveRefCntPtr FS) : FileOptionsBaseProvider(GlobalOptions, DefaultOptions, OverrideOptions, FS), ConfigOptions(ConfigOptions) {} std::vector ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) { std::vector RawOptions = DefaultOptionsProvider::getRawOptions(FileName); if (ConfigOptions.InheritParentConfig.getValueOr(false)) { LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); assert(FS && "FS must be set."); llvm::SmallString<128> AbsoluteFilePath(FileName); if (!FS->makeAbsolute(AbsoluteFilePath)) { addRawFileOptions(AbsoluteFilePath, RawOptions); } } RawOptions.emplace_back(ConfigOptions, OptionsSourceTypeConfigCommandLineOption); RawOptions.emplace_back(OverrideOptions, OptionsSourceTypeCheckCommandLineOption); return RawOptions; } FileOptionsBaseProvider::FileOptionsBaseProvider( const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, const ClangTidyOptions &OverrideOptions, llvm::IntrusiveRefCntPtr VFS) : DefaultOptionsProvider(GlobalOptions, DefaultOptions), OverrideOptions(OverrideOptions), FS(std::move(VFS)) { if (!FS) FS = llvm::vfs::getRealFileSystem(); ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); } FileOptionsBaseProvider::FileOptionsBaseProvider( const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, const ClangTidyOptions &OverrideOptions, const FileOptionsBaseProvider::ConfigFileHandlers &ConfigHandlers) : DefaultOptionsProvider(GlobalOptions, DefaultOptions), OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {} void FileOptionsBaseProvider::addRawFileOptions( llvm::StringRef AbsolutePath, std::vector &CurOptions) { auto CurSize = CurOptions.size(); // 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(AbsolutePath); for (StringRef CurrentPath = Path; !CurrentPath.empty(); CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { llvm::Optional 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) { LLVM_DEBUG(llvm::dbgs() << "Caching configuration for path " << Path << ".\n"); if (!CachedOptions.count(Path)) CachedOptions[Path] = *Result; Path = llvm::sys::path::parent_path(Path); } CachedOptions[Path] = *Result; CurOptions.push_back(*Result); if (!Result->first.InheritParentConfig.getValueOr(false)) break; } } // Reverse order of file configs because closer configs should have higher // priority. std::reverse(CurOptions.begin() + CurSize, CurOptions.end()); } FileOptionsProvider::FileOptionsProvider( const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, const ClangTidyOptions &OverrideOptions, llvm::IntrusiveRefCntPtr VFS) : FileOptionsBaseProvider(GlobalOptions, DefaultOptions, OverrideOptions, VFS){} FileOptionsProvider::FileOptionsProvider( const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, const ClangTidyOptions &OverrideOptions, const FileOptionsBaseProvider::ConfigFileHandlers &ConfigHandlers) : FileOptionsBaseProvider(GlobalOptions, DefaultOptions, OverrideOptions, 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 FileOptionsProvider::getRawOptions(StringRef FileName) { LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); assert(FS && "FS must be set."); llvm::SmallString<128> AbsoluteFilePath(FileName); if (FS->makeAbsolute(AbsoluteFilePath)) return {}; std::vector RawOptions = DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str()); addRawFileOptions(AbsoluteFilePath, RawOptions); OptionsSource CommandLineOptions(OverrideOptions, OptionsSourceTypeCheckCommandLineOption); RawOptions.push_back(CommandLineOptions); return RawOptions; } llvm::Optional FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) { assert(!Directory.empty()); llvm::ErrorOr DirectoryStatus = FS->status(Directory); if (!DirectoryStatus || !DirectoryStatus->isDirectory()) { 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); LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); llvm::ErrorOr FileStatus = FS->status(ConfigFile); if (!FileStatus || !FileStatus->isRegularFile()) continue; llvm::ErrorOr> Text = FS->getBufferForFile(ConfigFile); 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) { if (ParsedOptions.getError()) llvm::errs() << "Error parsing " << ConfigFile << ": " << ParsedOptions.getError().message() << "\n"; continue; } return OptionsSource(*ParsedOptions, std::string(ConfigFile)); } return llvm::None; } /// 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 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