blob: 6baca370e8731f434024a8ab6eab61645e8fb623 [file] [log] [blame]
//===--- 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;
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
LLVM_YAML_IS_SEQUENCE_VECTOR(ClangTidyOptions::StringPair)
LLVM_YAML_IS_SEQUENCE_VECTOR(std::string)
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("HeaderFilterRegex", Options.HeaderFilterRegex);
IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors);
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.HeaderFilterRegex = "";
Options.SystemHeaders = false;
Options.AnalyzeTemporaryDtors = false;
Options.User = llvm::None;
for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(),
E = ClangTidyModuleRegistry::end();
I != E; ++I)
Options = Options.mergeWith(I->instantiate()->getModuleOptions());
return Options;
}
ClangTidyOptions
ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const {
ClangTidyOptions Result = *this;
// Merge comma-separated glob lists by appending the new value after a comma.
if (Other.Checks)
Result.Checks =
(Result.Checks && !Result.Checks->empty() ? *Result.Checks + "," : "") +
*Other.Checks;
if (Other.HeaderFilterRegex)
Result.HeaderFilterRegex = Other.HeaderFilterRegex;
if (Other.SystemHeaders)
Result.SystemHeaders = Other.SystemHeaders;
if (Other.AnalyzeTemporaryDtors)
Result.AnalyzeTemporaryDtors = Other.AnalyzeTemporaryDtors;
if (Other.User)
Result.User = Other.User;
if (Other.ExtraArgs)
Result.ExtraArgs = Other.ExtraArgs;
if (Other.ExtraArgsBefore)
Result.ExtraArgsBefore = Other.ExtraArgsBefore;
for (const auto &KeyValue : Other.CheckOptions)
Result.CheckOptions[KeyValue.first] = KeyValue.second;
return Result;
}
FileOptionsProvider::FileOptionsProvider(
const ClangTidyGlobalOptions &GlobalOptions,
const ClangTidyOptions &DefaultOptions,
const ClangTidyOptions &OverrideOptions)
: DefaultOptionsProvider(GlobalOptions, DefaultOptions),
OverrideOptions(OverrideOptions) {
ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions);
}
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
// similar.
ClangTidyOptions FileOptionsProvider::getOptions(StringRef FileName) {
DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n");
SmallString<256> FilePath(FileName);
if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) {
llvm::errs() << "Can't make absolute path from " << FileName << ": "
<< EC.message() << "\n";
// FIXME: Figure out what to do.
} else {
FileName = FilePath;
}
// 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 = llvm::sys::path::parent_path(CurrentPath)) {
llvm::Optional<ClangTidyOptions> 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);
}
return CachedOptions[Path] = *Result;
}
}
}
llvm::Optional<ClangTidyOptions>
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 DefaultOptionsProvider::getOptions(Directory)
.mergeWith(*ParsedOptions)
.mergeWith(OverrideOptions);
}
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