Index: clang-tidy/ClangTidyDiagnosticConsumer.h =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.h +++ clang-tidy/ClangTidyDiagnosticConsumer.h @@ -60,12 +60,20 @@ /// \brief Filters checks by name. class ChecksFilter { public: - ChecksFilter(const ClangTidyOptions& Options); - bool isCheckEnabled(StringRef Name); + // GlobList is a comma-separated list of globs (only '*' metacharacter is + // supported) with optional '-' prefix to denote exclusion. + ChecksFilter(StringRef GlobList); + // Returns true if the check with the specified Name should be enabled. + // The result is the last matching glob's Positive flag. If Name is not + // matched by any globs, the check is not enabled. + bool isCheckEnabled(StringRef Name) { return isCheckEnabled(Name, false); } private: - llvm::Regex EnableChecks; - llvm::Regex DisableChecks; + bool isCheckEnabled(StringRef Name, bool Enabled); + + bool Positive; + llvm::Regex Regex; + std::unique_ptr NextFilter; }; struct ClangTidyStats { Index: clang-tidy/ClangTidyDiagnosticConsumer.cpp =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -111,16 +111,49 @@ ClangTidyError::ClangTidyError(StringRef CheckName) : CheckName(CheckName) {} -ChecksFilter::ChecksFilter(const ClangTidyOptions &Options) - : EnableChecks(Options.EnableChecksRegex), - DisableChecks(Options.DisableChecksRegex) {} +// Returns true if GlobList starts with the negative indicator ('-'), removes it +// from the GlobList. +static bool ConsumeNegativeIndicator(StringRef &GlobList) { + if (GlobList.startswith("-")) { + GlobList = GlobList.substr(1); + return true; + } + return false; +} +// Converts first glob from the comma-separated list of globs to Regex and +// removes it and the trailing comma from the GlobList. +static llvm::Regex ConsumeGlob(StringRef &GlobList) { + StringRef Glob = GlobList.substr(0, GlobList.find(',')); + GlobList = GlobList.substr(Glob.size() + 1); + llvm::SmallString<128> RegexText("^"); + StringRef MetaChars("()^$|*+?.[]\\{}"); + for (char C : Glob) { + if (C == '*') + RegexText.push_back('.'); + else if (MetaChars.find(C)) + RegexText.push_back('\\'); + RegexText.push_back(C); + } + RegexText.push_back('$'); + return llvm::Regex(RegexText); +} -bool ChecksFilter::isCheckEnabled(StringRef Name) { - return EnableChecks.match(Name) && !DisableChecks.match(Name); +ChecksFilter::ChecksFilter(StringRef GlobList) + : Positive(!ConsumeNegativeIndicator(GlobList)), + Regex(ConsumeGlob(GlobList)), + NextFilter(GlobList.empty() ? nullptr : new ChecksFilter(GlobList)) {} + +bool ChecksFilter::isCheckEnabled(StringRef Name, bool Enabled) { + if (Regex.match(Name)) + Enabled = Positive; + + if (NextFilter) + Enabled = NextFilter->isCheckEnabled(Name, Enabled); + return Enabled; } ClangTidyContext::ClangTidyContext(const ClangTidyOptions &Options) - : DiagEngine(nullptr), Options(Options), Filter(Options) {} + : DiagEngine(nullptr), Options(Options), Filter(Options.Checks) {} DiagnosticBuilder ClangTidyContext::diag( StringRef CheckName, SourceLocation Loc, StringRef Description, Index: clang-tidy/ClangTidyOptions.h =================================================================== --- clang-tidy/ClangTidyOptions.h +++ clang-tidy/ClangTidyOptions.h @@ -17,9 +17,8 @@ /// \brief Contains options for clang-tidy. struct ClangTidyOptions { - ClangTidyOptions() : EnableChecksRegex(".*"), AnalyzeTemporaryDtors(false) {} - std::string EnableChecksRegex; - std::string DisableChecksRegex; + ClangTidyOptions() : Checks("*"), AnalyzeTemporaryDtors(false) {} + std::string Checks; // Output warnings from headers matching this filter. Warnings from main files // will always be displayed. std::string HeaderFilterRegex; Index: clang-tidy/tool/ClangTidyMain.cpp =================================================================== --- clang-tidy/tool/ClangTidyMain.cpp +++ clang-tidy/tool/ClangTidyMain.cpp @@ -27,18 +27,17 @@ static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); -static cl::opt Checks( - "checks", - cl::desc("Regular expression matching the names of the checks to be run."), - cl::init(".*"), cl::cat(ClangTidyCategory)); -static cl::opt DisableChecks( - "disable-checks", - cl::desc("Regular expression matching the names of the checks to disable."), - cl::init("(clang-analyzer-alpha.*" // Too many false positives. - "|llvm-include-order" // Not implemented yet. - "|llvm-namespace-comment" // Not complete. - "|google-.*)"), // Doesn't apply to LLVM. - cl::cat(ClangTidyCategory)); +const char DefaultChecks[] = + "*," // Enable all checks, except these: + "-clang-analyzer-alpha*," // Too many false positives. + "-llvm-include-order," // Not implemented yet. + "-llvm-namespace-comment," // Not complete. + "-google-*,"; // Doesn't apply to LLVM. +static cl::opt +Checks("checks", + cl::desc("Comma-separated list of positive and negative globs matching\n" + "the names of the checks to be run."), + cl::init(""), cl::cat(ClangTidyCategory)); static cl::opt HeaderFilter( "header-filter", cl::desc("Regular expression matching the names of the headers to output\n" @@ -88,8 +87,7 @@ CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory); clang::tidy::ClangTidyOptions Options; - Options.EnableChecksRegex = Checks; - Options.DisableChecksRegex = DisableChecks; + Options.Checks = DefaultChecks + Checks; Options.HeaderFilterRegex = HeaderFilter; Options.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; Index: test/clang-tidy/arg-comments.cpp =================================================================== --- test/clang-tidy/arg-comments.cpp +++ test/clang-tidy/arg-comments.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy --checks=misc-argument-comment %s -- | FileCheck %s +// RUN: clang-tidy --checks='-*,misc-argument-comment' %s -- | FileCheck %s // FIXME: clang-tidy should provide a -verify mode to make writing these checks // easier and more accurate. Index: test/clang-tidy/basic.cpp =================================================================== --- test/clang-tidy/basic.cpp +++ test/clang-tidy/basic.cpp @@ -1,5 +1,5 @@ // RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp -// RUN: clang-tidy %t.cpp -checks='llvm-namespace-comment' -disable-checks='' -- > %t2.cpp +// RUN: clang-tidy %t.cpp -checks='-*,llvm-namespace-comment' -- > %t2.cpp // RUN: FileCheck -input-file=%t2.cpp %s namespace i { Index: test/clang-tidy/check_clang_tidy_fix.sh =================================================================== --- test/clang-tidy/check_clang_tidy_fix.sh +++ test/clang-tidy/check_clang_tidy_fix.sh @@ -7,6 +7,5 @@ TEMPORARY_FILE=$3.cpp grep -Ev "// *[A-Z-]+:" ${INPUT_FILE} > ${TEMPORARY_FILE} -clang-tidy ${TEMPORARY_FILE} -fix --checks=${CHECK_TO_RUN} \ - --disable-checks="" -- --std=c++11 +clang-tidy ${TEMPORARY_FILE} -fix --checks="-*,${CHECK_TO_RUN}" -- --std=c++11 FileCheck -input-file=${TEMPORARY_FILE} ${INPUT_FILE} Index: test/clang-tidy/check_clang_tidy_output.sh =================================================================== --- test/clang-tidy/check_clang_tidy_output.sh +++ test/clang-tidy/check_clang_tidy_output.sh @@ -5,6 +5,5 @@ INPUT_FILE=$1 CHECK_TO_RUN=$2 -clang-tidy --checks=${CHECK_TO_RUN} --disable-checks="" ${INPUT_FILE} \ - -- --std=c++11 -x c++ \ +clang-tidy --checks="-*,${CHECK_TO_RUN}" ${INPUT_FILE} -- --std=c++11 -x c++ \ | FileCheck ${INPUT_FILE} Index: test/clang-tidy/deduplication.cpp =================================================================== --- test/clang-tidy/deduplication.cpp +++ test/clang-tidy/deduplication.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy -checks=google-explicit-constructor -disable-checks='' %s -- | FileCheck %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor' %s -- | FileCheck %s template class A { A(T); }; Index: test/clang-tidy/diagnostic.cpp =================================================================== --- test/clang-tidy/diagnostic.cpp +++ test/clang-tidy/diagnostic.cpp @@ -1,7 +1,7 @@ -// RUN: clang-tidy -disable-checks='' %s.nonexistent.cpp -- | FileCheck -check-prefix=CHECK1 %s -// RUN: clang-tidy -disable-checks='' %s -- -fan-unknown-option | FileCheck -check-prefix=CHECK2 %s -// RUN: clang-tidy -checks='(google-explicit-constructor|clang-diagnostic-literal-conversion)' -disable-checks='' %s -- -fan-unknown-option | FileCheck -check-prefix=CHECK3 %s -// RUN: clang-tidy -checks='clang-diagnostic-macro-redefined' -disable-checks='' %s -- -DMACRO_FROM_COMMAND_LINE | FileCheck -check-prefix=CHECK4 %s +// RUN: clang-tidy %s.nonexistent.cpp -- | FileCheck -check-prefix=CHECK1 %s +// RUN: clang-tidy -checks='google-explicit-constructor' %s -- -fan-unknown-option | FileCheck -check-prefix=CHECK2 %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor,clang-diagnostic-literal-conversion' %s -- -fan-unknown-option | FileCheck -check-prefix=CHECK3 %s +// RUN: clang-tidy -checks='-*,clang-diagnostic-macro-redefined' %s -- -DMACRO_FROM_COMMAND_LINE | FileCheck -check-prefix=CHECK4 %s // CHECK1-NOT: warning // CHECK2-NOT: warning Index: test/clang-tidy/file-filter.cpp =================================================================== --- test/clang-tidy/file-filter.cpp +++ test/clang-tidy/file-filter.cpp @@ -1,6 +1,6 @@ -// RUN: clang-tidy -checks=google-explicit-constructor -disable-checks='' -header-filter='' %s -- -I %S/Inputs/file-filter 2>&1 | FileCheck %s -// RUN: clang-tidy -checks=google-explicit-constructor -disable-checks='' -header-filter='.*' %s -- -I %S/Inputs/file-filter 2>&1 | FileCheck --check-prefix=CHECK2 %s -// RUN: clang-tidy -checks=google-explicit-constructor -disable-checks='' -header-filter='header2\.h' %s -- -I %S/Inputs/file-filter 2>&1 | FileCheck --check-prefix=CHECK3 %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='' %s -- -I %S/Inputs/file-filter 2>&1 | FileCheck %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' %s -- -I %S/Inputs/file-filter 2>&1 | FileCheck --check-prefix=CHECK2 %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='header2\.h' %s -- -I %S/Inputs/file-filter 2>&1 | FileCheck --check-prefix=CHECK3 %s #include "header1.h" // CHECK-NOT: warning: Index: test/clang-tidy/fix.cpp =================================================================== --- test/clang-tidy/fix.cpp +++ test/clang-tidy/fix.cpp @@ -1,5 +1,5 @@ // RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp -// RUN: clang-tidy %t.cpp -checks='(google-explicit-constructor|llvm-namespace-comment)' -disable-checks='' -fix -- > %t.msg 2>&1 +// RUN: clang-tidy %t.cpp -checks='-*,google-explicit-constructor,llvm-namespace-comment' -fix -- > %t.msg 2>&1 // RUN: FileCheck -input-file=%t.cpp %s // RUN: FileCheck -input-file=%t.msg -check-prefix=CHECK-MESSAGES %s Index: test/clang-tidy/macros.cpp =================================================================== --- test/clang-tidy/macros.cpp +++ test/clang-tidy/macros.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy -checks=google-explicit-constructor -disable-checks='' %s -- | FileCheck %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor' %s -- | FileCheck %s #define Q(name) class name { name(int i); } Index: test/clang-tidy/nolint.cpp =================================================================== --- test/clang-tidy/nolint.cpp +++ test/clang-tidy/nolint.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy -checks=google-explicit-constructor -disable-checks='' %s -- 2>&1 | FileCheck %s +// RUN: clang-tidy -checks='-*,google-explicit-constructor' %s -- 2>&1 | FileCheck %s class A { A(int i); }; // CHECK: :[[@LINE-1]]:11: warning: Single-argument constructors must be explicit [google-explicit-constructor] Index: test/clang-tidy/select-checks.cpp =================================================================== --- test/clang-tidy/select-checks.cpp +++ test/clang-tidy/select-checks.cpp @@ -1,5 +1,5 @@ // RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp -// RUN: clang-tidy %t.cpp -fix -checks=^llvm-.* -disable-checks='' -- +// RUN: clang-tidy %t.cpp -fix -checks='-*,llvm-*' -- // RUN: FileCheck -input-file=%t.cpp %s namespace i { Index: test/clang-tidy/static-analyzer.cpp =================================================================== --- test/clang-tidy/static-analyzer.cpp +++ test/clang-tidy/static-analyzer.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy %s -checks='clang-analyzer-.*' -disable-checks='alpha' -- | FileCheck %s +// RUN: clang-tidy %s -checks='-*,clang-analyzer-*,-clang-analyzer-alpha*' -- | FileCheck %s extern void *malloc(unsigned long); extern void free(void *); Index: test/clang-tidy/temporaries.cpp =================================================================== --- test/clang-tidy/temporaries.cpp +++ test/clang-tidy/temporaries.cpp @@ -1,4 +1,4 @@ -// RUN: clang-tidy -checks=clang-analyzer-core.NullDereference -disable-checks='' -analyze-temporary-dtors %s -- | FileCheck %s +// RUN: clang-tidy -checks='-*,clang-analyzer-core.NullDereference' -analyze-temporary-dtors %s -- | FileCheck %s struct NoReturnDtor { ~NoReturnDtor() __attribute__((noreturn)); Index: unittests/clang-tidy/ClangTidyDiagnosticConsumerTest.cpp =================================================================== --- unittests/clang-tidy/ClangTidyDiagnosticConsumerTest.cpp +++ unittests/clang-tidy/ClangTidyDiagnosticConsumerTest.cpp @@ -28,6 +28,59 @@ EXPECT_EQ("variable []", Errors[1].Message.Message); } +TEST(ChecksFilter, Empty) { + ChecksFilter Filter(""); + + EXPECT_TRUE(Filter.isCheckEnabled("")); + EXPECT_FALSE(Filter.isCheckEnabled("aaa")); +} + +TEST(ChecksFilter, Nothing) { + ChecksFilter Filter("-*"); + + EXPECT_FALSE(Filter.isCheckEnabled("")); + EXPECT_FALSE(Filter.isCheckEnabled("a")); + EXPECT_FALSE(Filter.isCheckEnabled("-*")); + EXPECT_FALSE(Filter.isCheckEnabled("-")); + EXPECT_FALSE(Filter.isCheckEnabled("*")); +} + +TEST(ChecksFilter, Everything) { + ChecksFilter Filter("*"); + + EXPECT_TRUE(Filter.isCheckEnabled("")); + EXPECT_TRUE(Filter.isCheckEnabled("aaaa")); + EXPECT_TRUE(Filter.isCheckEnabled("-*")); + EXPECT_TRUE(Filter.isCheckEnabled("-")); + EXPECT_TRUE(Filter.isCheckEnabled("*")); +} + +TEST(ChecksFilter, Simple) { + ChecksFilter Filter("aaa"); + + EXPECT_TRUE(Filter.isCheckEnabled("aaa")); + EXPECT_FALSE(Filter.isCheckEnabled("")); + EXPECT_FALSE(Filter.isCheckEnabled("aa")); + EXPECT_FALSE(Filter.isCheckEnabled("aaaa")); + EXPECT_FALSE(Filter.isCheckEnabled("bbb")); +} + +TEST(ChecksFilter, Complex) { + ChecksFilter Filter("*,-a.*,-b.*,a.a.*,-a.a.a.*,-..,-...,-..+,-*$,-*qwe*"); + + EXPECT_TRUE(Filter.isCheckEnabled("aaa")); + EXPECT_TRUE(Filter.isCheckEnabled("qqq")); + EXPECT_FALSE(Filter.isCheckEnabled("a.")); + EXPECT_FALSE(Filter.isCheckEnabled("a.b")); + EXPECT_FALSE(Filter.isCheckEnabled("b.")); + EXPECT_FALSE(Filter.isCheckEnabled("b.b")); + EXPECT_TRUE(Filter.isCheckEnabled("a.a.b")); + EXPECT_FALSE(Filter.isCheckEnabled("a.a.a.a")); + EXPECT_FALSE(Filter.isCheckEnabled("qwe")); + EXPECT_FALSE(Filter.isCheckEnabled("asdfqweasdf")); + EXPECT_TRUE(Filter.isCheckEnabled("asdfqwEasdf")); +} + } // namespace test } // namespace tidy } // namespace clang