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 @@ -84,17 +84,72 @@ template <> void yamlize(IO &IO, ClangTidyOptions::OptionMap &Options, bool, EmptyContext &Ctx) { + + bool UseDefault; + bool UseDefault2; + void *SaveInfo; + void *SaveInfo2; if (IO.outputting()) { + // Nested option names need to be converted to null terminated strings for + // yaml serialization + llvm::SmallString<64> Buffer; + auto ToCString = [&Buffer](StringRef Str) { + Buffer.assign(Str); + return Buffer.c_str(); + }; IO.beginMapping(); - // Only output as a map - for (auto &Key : Options) { - bool UseDefault; - void *SaveInfo; - IO.preflightKey(Key.getKey().data(), true, false, UseDefault, SaveInfo); - StringRef S = Key.getValue().Value; + // Using a map here means we have a deterministic order + std::map>> + LocalItems; + SmallVector, 8> GlobalItems; + for (auto &Entry : Options) { + auto Key = Entry.getKey(); + // Look for a '.' seperator in the key name + auto Idx = Key.find('.'); + if (Idx == StringRef::npos) { + GlobalItems.emplace_back(Key, Entry.getValue().Value); + } else { + LocalItems[Key.take_front(Idx)].emplace_back(Key.drop_front(Idx + 1), + Entry.getValue().Value); + } + } + llvm::sort(GlobalItems, + [](auto &Lhs, auto &Rhs) { return Lhs.first < Rhs.first; }); + for (auto &Item : GlobalItems) { + IO.preflightKey(Item.first.data(), true, false, UseDefault, SaveInfo); + StringRef S = Item.second; IO.scalarString(S, needsQuotes(S)); IO.postflightKey(SaveInfo); } + for (auto &Item : LocalItems) { + // If a check only has 1 option, Emit it directly as a scalar. + if (Item.second.size() == 1) { + // This is a bit of a hack as Item.first only contains the check name, + // but the null terminated buffer it references will contain the whole + // option name. + IO.preflightKey(Item.first.data(), true, false, UseDefault, SaveInfo); + StringRef S = Item.second.front().second; + IO.scalarString(S, needsQuotes(S)); + IO.postflightKey(SaveInfo); + continue; + } + // Sort all of the checks options for nicer output + llvm::sort(Item.second, + [](auto &Lhs, auto &Rhs) { return Lhs.first < Rhs.first; }); + // The stringref in Item.first is not null terminated, so we need to copy + // to a null terminated buffer + IO.preflightKey(ToCString(Item.first), true, false, UseDefault, SaveInfo); + IO.beginMapping(); + for (auto &SubItem : Item.second) { + IO.preflightKey(SubItem.first.data(), true, false, UseDefault2, + SaveInfo2); + StringRef S = SubItem.second; + IO.scalarString(S, needsQuotes(S)); + IO.postflightKey(SaveInfo2); + } + IO.endMapping(); + IO.postflightKey(SaveInfo); + } IO.endMapping(); } else { // We need custom logic here to support the old method of specifying check @@ -107,8 +162,27 @@ yamlize(IO, NOpts->Options, true, Ctx); } else if (isa(I.getCurrentNode())) { IO.beginMapping(); + llvm::SmallString<128> Buffer; for (StringRef Key : IO.keys()) { - IO.mapRequired(Key.data(), Options[Key].Value); + IO.preflightKey(Key.data(), true, false, UseDefault, SaveInfo); + if (isa(I.getCurrentNode())) { + yamlize(IO, Options[Key].Value, true, Ctx); + } else if (isa(I.getCurrentNode())) { + IO.beginMapping(); + Buffer.assign({Key, "."}); + auto Size = Buffer.size(); + for (StringRef Key : IO.keys()) { + IO.preflightKey(Key.data(), true, false, UseDefault2, SaveInfo2); + Buffer.truncate(Size); + Buffer.append(Key); + yamlize(I, Options[Buffer].Value, true, Ctx); + IO.postflightKey(SaveInfo2); + } + IO.endMapping(); + } else { + IO.setError("Expected a scalar or a map"); + } + IO.postflightKey(SaveInfo); } IO.endMapping(); } else { 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 @@ -52,6 +52,9 @@ options. Example: CheckOptions: some-check.SomeOption: 'some value' + multi-option-check: + Option1: 'option_1_value' + Option2: 'option_2_value' Checks - Same as '--checks'. ExtraArgs - Same as '--extra-args'. ExtraArgsBefore - Same as '--extra-args-before'. diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -98,6 +98,9 @@ `ImplementationFileExtensions`, replacing the check-local options of the same name. +- Ability to group `CheckOptions` for a specific check in the `.clang-tidy` + configuration file. + New checks ^^^^^^^^^^ 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 @@ -259,6 +259,9 @@ options. Example: CheckOptions: some-check.SomeOption: 'some value' + multi-option-check: + Option1: 'option_1_value' + Option2: 'option_2_value' Checks - Same as '--checks'. ExtraArgs - Same as '--extra-args'. ExtraArgsBefore - Same as '--extra-args-before'. diff --git a/clang-tools-extra/test/clang-tidy/checkers/google/module.cpp b/clang-tools-extra/test/clang-tidy/checkers/google/module.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/google/module.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/google/module.cpp @@ -1,6 +1,8 @@ // RUN: clang-tidy -checks='-*,google*' -config='{}' -dump-config - -- | FileCheck %s // CHECK: CheckOptions: -// CHECK-DAG: {{google-readability-braces-around-statements.ShortStatementLines: '1'}} -// CHECK-DAG: {{google-readability-function-size.StatementThreshold: '800'}} -// CHECK-DAG: {{google-readability-namespace-comments.ShortNamespaceLines: '10'}} -// CHECK-DAG: {{google-readability-namespace-comments.SpacesBeforeComments: '2'}} +// CHECK: {{ google-readability-braces-around-statements.ShortStatementLines: '1'}} +// CHECK: {{ google-readability-function-size:}} +// CHECK: {{ StatementThreshold: '800'}} +// CHECK: {{ google-readability-namespace-comments:}} +// CHECK-NEXT: {{ ShortNamespaceLines: '10'}} +// CHECK-NEXT: {{ SpacesBeforeComments: '2'}} 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 --- 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 @@ -7,3 +7,7 @@ value: 'true' - key: IgnoreMacros value: 'false' + - key: modernize-loop-convert.MakeReverseRangeFunction + value: Reverse + - key: modernize-loop-convert.UseCxx20ReverseRanges + value: false diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/key-dict/.clang-tidy b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/key-dict/.clang-tidy --- a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/key-dict/.clang-tidy +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/4/key-dict/.clang-tidy @@ -4,4 +4,7 @@ modernize-loop-convert.MaxCopySize: '20' llvm-qualified-auto.AddConstToQualified: 'true' IgnoreMacros: 'false' + modernize-loop-convert: + MakeReverseRangeFunction: Reverse + UseCxx20ReverseRanges: false 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 @@ -21,10 +21,14 @@ // RUN: clang-tidy -dump-config %S/Inputs/config-files/4/key-dict/- -- | FileCheck %s -check-prefix=CHECK-CHILD4 // CHECK-CHILD4: Checks: {{.*}}modernize-loop-convert,modernize-use-using,llvm-qualified-auto -// CHECK-CHILD4-DAG: llvm-qualified-auto.AddConstToQualified: 'true' -// CHECK-CHILD4-DAG: modernize-loop-convert.MaxCopySize: '20' -// CHECK-CHILD4-DAG: modernize-loop-convert.MinConfidence: reasonable -// CHECK-CHILD4-DAG: modernize-use-using.IgnoreMacros: 'false' +// CHECK-CHILD4: llvm-qualified-auto.AddConstToQualified: 'true' +// CHECK-CHILD4: modernize-loop-convert: +// CHECK-CHILD4-NEXT: IncludeStyle: llvm +// CHECK-CHILD4-NEXT: MakeReverseRangeFunction: Reverse +// CHECK-CHILD4-NEXT: MakeReverseRangeHeader: '' +// CHECK-CHILD4-NEXT: MaxCopySize: '20' +// CHECK-CHILD4-NEXT: MinConfidence: reasonable +// CHECK-CHILD4: modernize-use-using.IgnoreMacros: 'false' // 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. @@ -44,9 +48,10 @@ // RUN: %S/Inputs/config-files/4/44/- -- | FileCheck %s -check-prefix=CHECK-CHILD5 // CHECK-CHILD5: Checks: {{.*}}modernize-loop-convert,modernize-use-using,llvm-qualified-auto,-llvm-qualified-auto -// CHECK-CHILD5-DAG: modernize-loop-convert.MaxCopySize: '21' -// CHECK-CHILD5-DAG: modernize-loop-convert.MinConfidence: reasonable -// CHECK-CHILD5-DAG: modernize-use-using.IgnoreMacros: 'false' +// CHECK-CHILD5: modernize-loop-convert: +// CHECK-CHILD5: MaxCopySize: '21' +// CHECK-CHILD5: MinConfidence: reasonable +// CHECK-CHILD5: modernize-use-using.IgnoreMacros: 'false' // RUN: clang-tidy -dump-config \ // RUN: --config='{InheritParentConfig: false, \