diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -328,7 +328,9 @@ StringRef OptName(Opt.first); if (!OptName.startswith(AnalyzerPrefix)) continue; - AnalyzerOptions->Config[OptName.substr(AnalyzerPrefix.size())] = Opt.second; + // Analyzer options are always local options so we can ignore priority. + AnalyzerOptions->Config[OptName.substr(AnalyzerPrefix.size())] = + Opt.second.Value; } } diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp --- a/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp @@ -38,20 +38,21 @@ StringRef Default) const { const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str()); if (Iter != CheckOptions.end()) - return Iter->second; + return Iter->second.Value; return std::string(Default); } std::string ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, StringRef Default) const { - auto Iter = CheckOptions.find(NamePrefix + LocalName.str()); - if (Iter != CheckOptions.end()) - return Iter->second; - // Fallback to global setting, if present. - Iter = CheckOptions.find(LocalName.str()); - if (Iter != CheckOptions.end()) - return Iter->second; + auto IterLocal = CheckOptions.find(NamePrefix + LocalName.str()); + auto IterGlobal = CheckOptions.find(LocalName.str()); + if (IterLocal != CheckOptions.end() && + (IterGlobal == CheckOptions.end() || + IterGlobal->second.Priority < IterLocal->second.Priority)) + return IterLocal->second.Value; + if (IterGlobal != CheckOptions.end()) + return IterGlobal->second.Value; return std::string(Default); } diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -199,7 +199,7 @@ // Merge options on top of getDefaults() as a safeguard against options with // unset values. return ClangTidyOptions::getDefaults().mergeWith( - OptionsProvider->getOptions(File)); + OptionsProvider->getOptions(File), 0); } void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; } diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h @@ -58,7 +58,9 @@ /// Creates a new \c ClangTidyOptions instance combined from all fields /// of this instance overridden by the fields of \p Other that have a value. - ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const; + /// \p Order specifies precedence of \p Other option. + ClangTidyOptions mergeWith(const ClangTidyOptions &Other, + unsigned Order) const; /// Checks filter. llvm::Optional Checks; @@ -93,8 +95,19 @@ /// comments in the relevant check. llvm::Optional User; + /// Helper structure for storing option value with priority of the value. + struct ClangTidyValue { + ClangTidyValue() : Value(), Priority(0) {} + ClangTidyValue(const char *Value) : Value(Value), Priority(0) {} + ClangTidyValue(const std::string &Value) : Value(Value), Priority(0) {} + ClangTidyValue(const std::string &Value, unsigned Priority) + : Value(Value), Priority(Priority) {} + + std::string Value; + unsigned Priority; + }; typedef std::pair StringPair; - typedef std::map OptionMap; + typedef std::map OptionMap; /// Key-value mapping used to store check-specific options. OptionMap CheckOptions; @@ -106,6 +119,12 @@ /// Add extra compilation arguments to the start of the list. llvm::Optional ExtraArgsBefore; + + /// Only used in the FileOptionsProvider. If true, FileOptionsProvider will + /// take a configuration file in the parent directory (if any exists) and + /// apply this config file on top of the parent one. If false or missing, + /// only this configuration file will be used. + llvm::Optional InheritParentConfig; }; /// Abstract interface for retrieving various ClangTidy options. diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp @@ -67,12 +67,15 @@ struct NOptionMap { NOptionMap(IO &) {} - NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) - : Options(OptionMap.begin(), OptionMap.end()) {} + NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) { + Options.reserve(OptionMap.size()); + for (const auto &KeyValue : OptionMap) + Options.push_back(std::make_pair(KeyValue.first, KeyValue.second.Value)); + } ClangTidyOptions::OptionMap denormalize(IO &) { ClangTidyOptions::OptionMap Map; for (const auto &KeyValue : Options) - Map[KeyValue.first] = KeyValue.second; + Map[KeyValue.first] = ClangTidyOptions::ClangTidyValue(KeyValue.second); return Map; } std::vector Options; @@ -92,6 +95,7 @@ IO.mapOptional("CheckOptions", NOpts->Options); IO.mapOptional("ExtraArgs", Options.ExtraArgs); IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore); + IO.mapOptional("InheritParentConfig", Options.InheritParentConfig); } }; @@ -109,10 +113,12 @@ Options.SystemHeaders = false; Options.FormatStyle = "none"; Options.User = llvm::None; + unsigned Priority = 0; for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), E = ClangTidyModuleRegistry::end(); I != E; ++I) - Options = Options.mergeWith(I->instantiate()->getModuleOptions()); + Options = + Options.mergeWith(I->instantiate()->getModuleOptions(), ++Priority); return Options; } @@ -138,8 +144,8 @@ Dest = Src; } -ClangTidyOptions -ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const { +ClangTidyOptions ClangTidyOptions::mergeWith(const ClangTidyOptions &Other, + unsigned Priority) const { ClangTidyOptions Result = *this; mergeCommaSeparatedLists(Result.Checks, Other.Checks); @@ -151,8 +157,10 @@ mergeVectors(Result.ExtraArgs, Other.ExtraArgs); mergeVectors(Result.ExtraArgsBefore, Other.ExtraArgsBefore); - for (const auto &KeyValue : Other.CheckOptions) - Result.CheckOptions[KeyValue.first] = KeyValue.second; + for (const auto &KeyValue : Other.CheckOptions) { + Result.CheckOptions[KeyValue.first] = ClangTidyValue( + KeyValue.second.Value, KeyValue.second.Priority + Priority); + } return Result; } @@ -168,8 +176,9 @@ ClangTidyOptions ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) { ClangTidyOptions Result; + unsigned Priority = 0; for (const auto &Source : getRawOptions(FileName)) - Result = Result.mergeWith(Source.first); + Result = Result.mergeWith(Source.first, ++Priority); return Result; } @@ -237,6 +246,7 @@ DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str()); OptionsSource CommandLineOptions(OverrideOptions, OptionsSourceTypeCheckCommandLineOption); + size_t FirstFileConfig = RawOptions.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(AbsoluteFilePath.str()); @@ -256,15 +266,21 @@ while (Path != CurrentPath) { LLVM_DEBUG(llvm::dbgs() << "Caching configuration for path " << Path << ".\n"); - CachedOptions[Path] = *Result; + if (!CachedOptions.count(Path)) + CachedOptions[Path] = *Result; Path = llvm::sys::path::parent_path(Path); } CachedOptions[Path] = *Result; RawOptions.push_back(*Result); - break; + if (!Result->first.InheritParentConfig || + !*Result->first.InheritParentConfig) + break; } } + // Reverse order of file configs because closer configs should have higher + // priority. + std::reverse(RawOptions.begin() + FirstFileConfig, RawOptions.end()); RawOptions.push_back(CommandLineOptions); return RawOptions; } diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -36,17 +36,20 @@ Configuration files: clang-tidy attempts to read configuration for each source file from a .clang-tidy file located in the closest parent directory of the source - file. If any configuration options have a corresponding command-line - option, command-line option takes precedence. The effective - configuration can be inspected using -dump-config: + file. If InheritParentConfig is true in a config file, the configuration file + in the parent directory (if any exists) will be taken and current config file + will be applied on top of the parent one. If any configuration options have + a corresponding command-line option, command-line option takes precedence. + The effective configuration can be inspected using -dump-config: $ clang-tidy -dump-config --- - Checks: '-*,some-check' - WarningsAsErrors: '' - HeaderFilterRegex: '' - FormatStyle: none - User: user + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFilterRegex: '' + FormatStyle: none + InheritParentConfig: true + User: user CheckOptions: - key: some-check.SomeOption value: 'some value' @@ -294,7 +297,7 @@ parseConfiguration(Config)) { return std::make_unique( GlobalOptions, - ClangTidyOptions::getDefaults().mergeWith(DefaultOptions), + ClangTidyOptions::getDefaults().mergeWith(DefaultOptions, 0), *ParsedConfig, OverrideOptions); } else { llvm::errs() << "Error: invalid configuration specified.\n" @@ -406,7 +409,7 @@ getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers); llvm::outs() << configurationAsText( ClangTidyOptions::getDefaults().mergeWith( - EffectiveOptions)) + EffectiveOptions, 0)) << "\n"; return 0; } diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst --- a/clang-tools-extra/docs/clang-tidy/index.rst +++ b/clang-tools-extra/docs/clang-tidy/index.rst @@ -247,17 +247,20 @@ Configuration files: clang-tidy attempts to read configuration for each source file from a .clang-tidy file located in the closest parent directory of the source - file. If any configuration options have a corresponding command-line - option, command-line option takes precedence. The effective - configuration can be inspected using -dump-config: + file. If InheritParentConfig is true in a config file, the configuration file + in the parent directory (if any exists) will be taken and current config file + will be applied on top of the parent one. If any configuration options have + a corresponding command-line option, command-line option takes precedence. + The effective configuration can be inspected using -dump-config: $ clang-tidy -dump-config --- - Checks: '-*,some-check' - WarningsAsErrors: '' - HeaderFilterRegex: '' - FormatStyle: none - User: user + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFilterRegex: '' + FormatStyle: none + InheritParentConfig: true + User: user CheckOptions: - key: some-check.SomeOption value: 'some value' diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/3/.clang-tidy b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/3/.clang-tidy new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/3/.clang-tidy @@ -0,0 +1,3 @@ +InheritParentConfig: true +Checks: 'from-child3' +HeaderFilterRegex: 'child3' diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/.clang-tidy b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/.clang-tidy new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/.clang-tidy @@ -0,0 +1,8 @@ +Checks: '-*,modernize-loop-convert,modernize-use-using' +CheckOptions: + - key: modernize-loop-convert.MaxCopySize + value: '10' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-use-using.IgnoreMacros + value: 1 diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/44/.clang-tidy b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/44/.clang-tidy new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/44/.clang-tidy @@ -0,0 +1,9 @@ +InheritParentConfig: true +Checks: 'llvm-qualified-auto' +CheckOptions: + - key: modernize-loop-convert.MaxCopySize + value: '20' + - key: llvm-qualified-auto.AddConstToQualified + value: '1' + - key: IgnoreMacros + value: '0' diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/config-files.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/config-files.cpp --- a/clang-tools-extra/test/clang-tidy/infrastructure/config-files.cpp +++ b/clang-tools-extra/test/clang-tidy/infrastructure/config-files.cpp @@ -7,6 +7,26 @@ // RUN: clang-tidy -dump-config %S/Inputs/config-files/2/- -- | FileCheck %s -check-prefix=CHECK-CHILD2 // CHECK-CHILD2: Checks: {{.*}}from-parent // CHECK-CHILD2: HeaderFilterRegex: parent +// RUN: clang-tidy -dump-config %S/Inputs/config-files/3/- -- | FileCheck %s -check-prefix=CHECK-CHILD3 +// CHECK-CHILD3: Checks: {{.*}}from-parent,from-child3 +// CHECK-CHILD3: HeaderFilterRegex: child3 // RUN: clang-tidy -dump-config -checks='from-command-line' -header-filter='from command line' %S/Inputs/config-files/- -- | FileCheck %s -check-prefix=CHECK-COMMAND-LINE // CHECK-COMMAND-LINE: Checks: {{.*}}from-parent,from-command-line // CHECK-COMMAND-LINE: HeaderFilterRegex: from command line + +// For this test we have to use names of the real checks because otherwise values are ignored. +// RUN: clang-tidy -dump-config %S/Inputs/config-files/4/44/- -- | FileCheck %s -check-prefix=CHECK-CHILD4 +// CHECK-CHILD4: Checks: {{.*}}modernize-loop-convert,modernize-use-using,llvm-qualified-auto +// CHECK-CHILD4: - key: llvm-qualified-auto.AddConstToQualified +// CHECK-CHILD4-NEXT: value: '1 +// CHECK-CHILD4: - key: modernize-loop-convert.MaxCopySize +// CHECK-CHILD4-NEXT: value: '20' +// CHECK-CHILD4: - key: modernize-loop-convert.MinConfidence +// CHECK-CHILD4-NEXT: value: reasonable +// CHECK-CHILD4: - key: modernize-use-using.IgnoreMacros +// CHECK-CHILD4-NEXT: value: '0' + +// RUN: clang-tidy --explain-config %S/Inputs/config-files/4/44/- -- | FileCheck %s -check-prefix=CHECK-EXPLAIN +// CHECK-EXPLAIN: 'llvm-qualified-auto' is enabled in the {{.*}}/Inputs/config-files/4/44/.clang-tidy. +// CHECK-EXPLAIN: 'modernize-loop-convert' is enabled in the {{.*}}/Inputs/config-files/4/.clang-tidy. +// CHECK-EXPLAIN: 'modernize-use-using' is enabled in the {{.*}}/Inputs/config-files/4/.clang-tidy. diff --git a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp --- a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp @@ -85,7 +85,7 @@ ExtraArgsBefore: ['arg-before3', 'arg-before4'] )"); ASSERT_TRUE(!!Options2); - ClangTidyOptions Options = Options1->mergeWith(*Options2); + ClangTidyOptions Options = Options1->mergeWith(*Options2, 0); EXPECT_EQ("check1,check2,check3,check4", *Options.Checks); EXPECT_EQ("filter2", *Options.HeaderFilterRegex); EXPECT_EQ("user2", *Options.User);