Index: clang-tidy/ClangTidyOptions.h =================================================================== --- clang-tidy/ClangTidyOptions.h +++ clang-tidy/ClangTidyOptions.h @@ -99,14 +99,37 @@ /// \brief Abstract interface for retrieving various ClangTidy options. class ClangTidyOptionsProvider { public: + static constexpr char OptionsSourceTypeDefaultBinary[] = "clang-tidy binary"; + static constexpr char OptionsSourceTypeCheckCommandLineOption[] = + "command-line option '-checks'"; + static constexpr char OptionsSourceTypeConfigCommandLineOption[] = + "command-line option '-config'"; + virtual ~ClangTidyOptionsProvider() {} /// \brief Returns global options, which are independent of the file. virtual const ClangTidyGlobalOptions &getGlobalOptions() = 0; + /// \brief ClangTidyOptions and its source. + // + /// clang-tidy has 3 types of the sources (from low to top): + /// * clang-tidy binary + /// * '-config' commandline option or a specific configuration file + /// * '-checks' commandline option + typedef std::pair OptionsSource; + + /// \brief Returns an ordered vector of OptionsSources, from low to top. + virtual std::vector getRawOptions( + llvm::StringRef FileName) = 0; + /// \brief Returns options applying to a specific translation unit with the /// specified \p FileName. - virtual ClangTidyOptions getOptions(llvm::StringRef FileName) = 0; + ClangTidyOptions getOptions(llvm::StringRef FileName) { + ClangTidyOptions Result; + for (const auto &Source: getRawOptions(FileName)) + Result = Result.mergeWith(Source.first); + return Result; + } }; /// \brief Implementation of the \c ClangTidyOptionsProvider interface, which @@ -119,15 +142,30 @@ const ClangTidyGlobalOptions &getGlobalOptions() override { return GlobalOptions; } - ClangTidyOptions getOptions(llvm::StringRef /*FileName*/) override { - return DefaultOptions; - } + std::vector getRawOptions( + llvm::StringRef FileName) override; private: ClangTidyGlobalOptions GlobalOptions; ClangTidyOptions DefaultOptions; }; +/// \brief Implementation of ClangTidyOptions interface, which is used for +/// '-config' command-line option. +class ConfigOptionsProvider : public DefaultOptionsProvider { +public: + ConfigOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &ConfigOptions, + const ClangTidyOptions &OverrideOptions); + std::vector getRawOptions( + llvm::StringRef FileName) override; + +private: + ClangTidyOptions ConfigOptions; + ClangTidyOptions OverrideOptions; +}; + /// \brief Implementation of the \c ClangTidyOptionsProvider interface, which /// tries to find a configuration file in the closest parent directory of each /// source file. @@ -198,14 +236,15 @@ const ClangTidyOptions &OverrideOptions, const ConfigFileHandlers &ConfigHandlers); - ClangTidyOptions getOptions(llvm::StringRef FileName) override; + std::vector getRawOptions( + llvm::StringRef FileName) override; protected: /// \brief Try to read configuration files from \p Directory using registered /// \c ConfigHandlers. - llvm::Optional TryReadConfigFile(llvm::StringRef Directory); + llvm::Optional TryReadConfigFile(llvm::StringRef Directory); - llvm::StringMap CachedOptions; + llvm::StringMap CachedOptions; ClangTidyOptions OverrideOptions; ConfigFileHandlers ConfigHandlers; }; Index: clang-tidy/ClangTidyOptions.cpp =================================================================== --- clang-tidy/ClangTidyOptions.cpp +++ clang-tidy/ClangTidyOptions.cpp @@ -23,6 +23,7 @@ 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) @@ -151,6 +152,35 @@ return Result; } +constexpr char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[]; +constexpr char + ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[]; +constexpr char + ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[]; + +std::vector +DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) { + return {OptionsSource(DefaultOptions, OptionsSourceTypeDefaultBinary)}; +} + +ConfigOptionsProvider::ConfigOptionsProvider( + const ClangTidyGlobalOptions& GlobalOptions, + const ClangTidyOptions& DefaultOptions, + const ClangTidyOptions& ConfigOptions, + const ClangTidyOptions& OverrideOptions) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + ConfigOptions(ConfigOptions), OverrideOptions(OverrideOptions) { +} + +std::vector ConfigOptionsProvider::getRawOptions( + llvm::StringRef FileName) { + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(FileName); + RawOptions.push_back(OptionsSource(ConfigOptions, OptionsSourceTypeConfigCommandLineOption)); + RawOptions.push_back(OptionsSource(OverrideOptions, OptionsSourceTypeCheckCommandLineOption)); + return RawOptions; +} + FileOptionsProvider::FileOptionsProvider( const ClangTidyGlobalOptions &GlobalOptions, const ClangTidyOptions &DefaultOptions, @@ -158,7 +188,6 @@ : DefaultOptionsProvider(GlobalOptions, DefaultOptions), OverrideOptions(OverrideOptions) { ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); - CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions); } FileOptionsProvider::FileOptionsProvider( @@ -168,13 +197,13 @@ 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) { +std::vector +FileOptionsProvider::getRawOptions(StringRef FileName) { DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); SmallString<256> FilePath(FileName); @@ -186,16 +215,22 @@ FileName = FilePath; } + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(FileName); + OptionsSource CommandLineOptions = + OptionsSource(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;; + for (StringRef CurrentPath = Path; !CurrentPath.empty(); CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { - llvm::Optional Result; + llvm::Optional Result; auto Iter = CachedOptions.find(CurrentPath); - if (Iter != CachedOptions.end()) - Result = Iter->second; + if (Iter != CachedOptions.end()) { + RawOptions.push_back(Iter->second); + break; + } if (!Result) Result = TryReadConfigFile(CurrentPath); @@ -208,12 +243,17 @@ CachedOptions[Path] = *Result; Path = llvm::sys::path::parent_path(Path); } - return CachedOptions[Path] = *Result; + CachedOptions[Path] = *Result; + + RawOptions.push_back(*Result); + break; } } + RawOptions.push_back(CommandLineOptions); + return RawOptions; } -llvm::Optional +llvm::Optional FileOptionsProvider::TryReadConfigFile(StringRef Directory) { assert(!Directory.empty()); @@ -255,10 +295,7 @@ << ParsedOptions.getError().message() << "\n"; continue; } - - return DefaultOptionsProvider::getOptions(Directory) - .mergeWith(*ParsedOptions) - .mergeWith(OverrideOptions); + return OptionsSource(*ParsedOptions, ConfigFile.c_str()); } return llvm::None; } Index: clang-tidy/tool/ClangTidyMain.cpp =================================================================== --- clang-tidy/tool/ClangTidyMain.cpp +++ clang-tidy/tool/ClangTidyMain.cpp @@ -128,6 +128,12 @@ )"), cl::init(false), cl::cat(ClangTidyCategory)); +static cl::opt ExplainConfig("explain-config", cl::desc(R"( +for each enabled check explains, where it is enabled, i.e. in clang-tidy binary, +command line or a specific configuration file. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + static cl::opt Config("config", cl::desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks: '*', @@ -280,11 +286,10 @@ if (!Config.empty()) { if (llvm::ErrorOr ParsedConfig = parseConfiguration(Config)) { - return llvm::make_unique( - GlobalOptions, ClangTidyOptions::getDefaults() - .mergeWith(DefaultOptions) - .mergeWith(*ParsedConfig) - .mergeWith(OverrideOptions)); + return llvm::make_unique( + GlobalOptions, + ClangTidyOptions::getDefaults().mergeWith(DefaultOptions), + *ParsedConfig, OverrideOptions); } else { llvm::errs() << "Error: invalid configuration specified.\n" << ParsedConfig.getError().message() << "\n"; @@ -311,6 +316,23 @@ ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FileName); std::vector EnabledChecks = getCheckNames(EffectiveOptions); + if (ExplainConfig) { + // FIXME: Figure out a more elegant way to show other ClangTidyOptions' + // fields like ExtraArg. + std::vector + RawOptions = OptionsProvider->getRawOptions(FileName); + for (const std::string &Check : EnabledChecks) { + for (auto It = RawOptions.rbegin(); It != RawOptions.rend(); ++It) { + if (It->first.Checks && GlobList(*(It->first.Checks)).contains(Check)) { + llvm::outs() << "'" << Check << "' is enabled in the " << It->second + << ".\n"; + break; + } + } + } + return 0; + } + if (ListChecks) { llvm::outs() << "Enabled checks:"; for (auto CheckName : EnabledChecks) Index: test/clang-tidy/Inputs/explain-config/.clang-tidy =================================================================== --- /dev/null +++ test/clang-tidy/Inputs/explain-config/.clang-tidy @@ -0,0 +1 @@ +Checks: '-*,modernize-use-nullptr' Index: test/clang-tidy/explain-checks.cpp =================================================================== --- /dev/null +++ test/clang-tidy/explain-checks.cpp @@ -0,0 +1,14 @@ +// RUN: clang-tidy -checks=-*,modernize-use-nullptr -explain-config | FileCheck --check-prefix=CHECK-MESSAGE1 %s +// RUN: clang-tidy -config="{Checks: '-*,modernize-use-nullptr'}" -explain-config | FileCheck --check-prefix=CHECK-MESSAGE2 %s +// RUN: clang-tidy -checks=modernize-use-nullptr -config="{Checks: '-*,modernize-use-nullptr'}" -explain-config | FileCheck --check-prefix=CHECK-MESSAGE3 %s +// RUN: clang-tidy -checks=modernize-use-nullptr -config="{Checks: '-*,-modernize-use-nullptr'}" %S/Inputs/explain-config/a.cc -explain-config | FileCheck --check-prefix=CHECK-MESSAGE4 %s +// RUN: clang-tidy -checks=modernize-use-nullptr -config="{Checks: '-*,modernize-*'}" -explain-config | FileCheck --check-prefix=CHECK-MESSAGE5 %s +// RUN: clang-tidy -checks=modernize-use-nullptr -explain-config | FileCheck --check-prefix=CHECK-MESSAGE6 %s +// RUN: clang-tidy -explain-config %S/Inputs/explain-config/a.cc | grep "'modernize-use-nullptr' is enabled in the %S/Inputs/explain-config/.clang-tidy." + +// CHECK-MESSAGE1: 'modernize-use-nullptr' is enabled in the command-line option '-checks'. +// CHECK-MESSAGE2: 'modernize-use-nullptr' is enabled in the command-line option '-config'. +// CHECK-MESSAGE3: 'modernize-use-nullptr' is enabled in the command-line option '-checks'. +// CHECK-MESSAGE4: 'modernize-use-nullptr' is enabled in the command-line option '-checks'. +// CHECK-MESSAGE5: 'modernize-use-nullptr' is enabled in the command-line option '-checks'. +// CHECK-MESSAGE6: 'clang-analyzer-unix.API' is enabled in the clang-tidy binary.