text: //===--- 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/MemoryBufferRef.h"
  #include "llvm/Support/Path.h"
  #include "llvm/Support/YAMLTraits.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 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<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.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<ClangTidyOptions::StringPair> Options;
  };
  
  template <> struct MappingTraits<ClangTidyOptions> {
    static void mapping(IO &IO, ClangTidyOptions &Options) {
      MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> 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 <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,
                                                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<OptionsSource>
  DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
    std::vector<OptionsSource> Result;
    Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
    return Result;
  }
  
  ConfigOptionsProvider::ConfigOptionsProvider(
      ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
      ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions,
      llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
      : FileOptionsBaseProvider(std::move(GlobalOptions),
                                std::move(DefaultOptions),
                                std::move(OverrideOptions), std::move(FS)),
        ConfigOptions(std::move(ConfigOptions)) {}
  
  std::vector<OptionsSource>
  ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {
    std::vector<OptionsSource> 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(
      ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
      ClangTidyOptions OverrideOptions,
      llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
      : DefaultOptionsProvider(std::move(GlobalOptions),
                               std::move(DefaultOptions)),
        OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) {
    if (!FS)
      FS = llvm::vfs::getRealFileSystem();
    ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
  }
  
  FileOptionsBaseProvider::FileOptionsBaseProvider(
      ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
      ClangTidyOptions OverrideOptions,
      FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
      : DefaultOptionsProvider(std::move(GlobalOptions),
                               std::move(DefaultOptions)),
        OverrideOptions(std::move(OverrideOptions)),
        ConfigHandlers(std::move(ConfigHandlers)) {}
  
  void FileOptionsBaseProvider::addRawFileOptions(
      llvm::StringRef AbsolutePath, std::vector<OptionsSource> &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<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) {
          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(
      ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
      ClangTidyOptions OverrideOptions,
      llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
      : FileOptionsBaseProvider(std::move(GlobalOptions),
                                std::move(DefaultOptions),
                                std::move(OverrideOptions), std::move(VFS)) {}
  
  FileOptionsProvider::FileOptionsProvider(
      ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
      ClangTidyOptions OverrideOptions,
      FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
      : FileOptionsBaseProvider(
            std::move(GlobalOptions), std::move(DefaultOptions),
            std::move(OverrideOptions), std::move(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) {
    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<OptionsSource> RawOptions =
        DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
    addRawFileOptions(AbsoluteFilePath, RawOptions);
    OptionsSource CommandLineOptions(OverrideOptions,
                                     OptionsSourceTypeCheckCommandLineOption);
  
    RawOptions.push_back(CommandLineOptions);
    return RawOptions;
  }
  
  llvm::Optional<OptionsSource>
  FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {
    assert(!Directory.empty());
  
    llvm::ErrorOr<llvm::vfs::Status> 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<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
  
      if (!FileStatus || !FileStatus->isRegularFile())
        continue;
  
      llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> 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<ClangTidyOptions> ParsedOptions =
          ConfigHandler.second({(*Text)->getBuffer(), ConfigFile});
      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<ClangTidyOptions>
  parseConfiguration(llvm::MemoryBufferRef Config) {
    llvm::yaml::Input Input(Config);
    ClangTidyOptions Options;
    Input >> Options;
    if (Input.error())
      return Input.error();
    return Options;
  }
  
  static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
    (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
  }
  
  llvm::ErrorOr<ClangTidyOptions>
  parseConfigurationWithDiags(llvm::MemoryBufferRef Config,
                              DiagCallback Handler) {
    llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,
                            &Handler);
    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
  