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 @@ -106,6 +106,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 @@ -92,6 +92,7 @@ IO.mapOptional("CheckOptions", NOpts->Options); IO.mapOptional("ExtraArgs", Options.ExtraArgs); IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore); + IO.mapOptional("InheritParentConfig", Options.InheritParentConfig); } }; @@ -138,6 +139,23 @@ Dest = Src; } +static bool isGlobalOption(llvm::StringRef Name) { + // Assume that global options don't have dot in the name. + return Name.find('.') == std::string::npos; +} + +static void overrideLocalOptions(ClangTidyOptions::OptionMap &Map, + llvm::StringRef Global) { + std::string Suffix = "."; + Suffix += Global; + for (auto I = Map.begin(); I != Map.end();) { + if (llvm::StringRef(I->first).endswith(Suffix)) + I = Map.erase(I); + else + ++I; + } +} + ClangTidyOptions ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const { ClangTidyOptions Result = *this; @@ -151,8 +169,16 @@ 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) { + if (isGlobalOption(KeyValue.first)) { + overrideLocalOptions(Result.CheckOptions, KeyValue.first); + Result.CheckOptions[KeyValue.first] = KeyValue.second; + } + } + for (const auto &KeyValue : Other.CheckOptions) { + if (!isGlobalOption(KeyValue.first)) + Result.CheckOptions[KeyValue.first] = KeyValue.second; + } return Result; } @@ -237,6 +263,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 +283,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' 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 @@ -244,17 +244,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.