Teach clang-tidy how to upgrade warnings into errors.

Similar in format to the `-checks=` argument, this new `-warnings-as-errors=`
argument upgrades any warnings emitted by the former to errors.

http://reviews.llvm.org/D15528

llvm-svn: 257642
This commit is contained in:
Jonathan Roelofs 2016-01-13 17:36:41 +00:00
parent 3f01e7a62e
commit d60388a985
10 changed files with 106 additions and 19 deletions

View File

@ -101,7 +101,8 @@ public:
Diags(IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, Diags(IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
DiagPrinter), DiagPrinter),
SourceMgr(Diags, Files), Rewrite(SourceMgr, LangOpts), SourceMgr(Diags, Files), Rewrite(SourceMgr, LangOpts),
ApplyFixes(ApplyFixes), TotalFixes(0), AppliedFixes(0) { ApplyFixes(ApplyFixes), TotalFixes(0), AppliedFixes(0),
WarningsAsErrors(0) {
DiagOpts->ShowColors = llvm::sys::Process::StandardOutHasColors(); DiagOpts->ShowColors = llvm::sys::Process::StandardOutHasColors();
DiagPrinter->BeginSourceFile(LangOpts); DiagPrinter->BeginSourceFile(LangOpts);
} }
@ -114,8 +115,14 @@ public:
SmallVector<std::pair<SourceLocation, bool>, 4> FixLocations; SmallVector<std::pair<SourceLocation, bool>, 4> FixLocations;
{ {
auto Level = static_cast<DiagnosticsEngine::Level>(Error.DiagLevel); auto Level = static_cast<DiagnosticsEngine::Level>(Error.DiagLevel);
std::string Name = Error.CheckName;
if (Error.IsWarningAsError) {
Name += ",-warnings-as-errors";
Level = DiagnosticsEngine::Error;
WarningsAsErrors++;
}
auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]")) auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]"))
<< Message.Message << Error.CheckName; << Message.Message << Name;
for (const tooling::Replacement &Fix : Error.Fix) { for (const tooling::Replacement &Fix : Error.Fix) {
SourceLocation FixLoc = getLocation(Fix.getFilePath(), Fix.getOffset()); SourceLocation FixLoc = getLocation(Fix.getFilePath(), Fix.getOffset());
SourceLocation FixEndLoc = FixLoc.getLocWithOffset(Fix.getLength()); SourceLocation FixEndLoc = FixLoc.getLocWithOffset(Fix.getLength());
@ -147,6 +154,8 @@ public:
} }
} }
unsigned getWarningsAsErrorsCount() const { return WarningsAsErrors; }
private: private:
SourceLocation getLocation(StringRef FilePath, unsigned Offset) { SourceLocation getLocation(StringRef FilePath, unsigned Offset) {
if (FilePath.empty()) if (FilePath.empty())
@ -174,6 +183,7 @@ private:
bool ApplyFixes; bool ApplyFixes;
unsigned TotalFixes; unsigned TotalFixes;
unsigned AppliedFixes; unsigned AppliedFixes;
unsigned WarningsAsErrors;
}; };
class ClangTidyASTConsumer : public MultiplexConsumer { class ClangTidyASTConsumer : public MultiplexConsumer {
@ -421,11 +431,13 @@ runClangTidy(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
return Context.getStats(); return Context.getStats();
} }
void handleErrors(const std::vector<ClangTidyError> &Errors, bool Fix) { void handleErrors(const std::vector<ClangTidyError> &Errors, bool Fix,
unsigned &WarningsAsErrorsCount) {
ErrorReporter Reporter(Fix); ErrorReporter Reporter(Fix);
for (const ClangTidyError &Error : Errors) for (const ClangTidyError &Error : Errors)
Reporter.reportDiagnostic(Error); Reporter.reportDiagnostic(Error);
Reporter.Finish(); Reporter.Finish();
WarningsAsErrorsCount += Reporter.getWarningsAsErrorsCount();
} }
void exportReplacements(const std::vector<ClangTidyError> &Errors, void exportReplacements(const std::vector<ClangTidyError> &Errors,

View File

@ -214,7 +214,8 @@ runClangTidy(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
// //
/// \brief Displays the found \p Errors to the users. If \p Fix is true, \p /// \brief Displays the found \p Errors to the users. If \p Fix is true, \p
/// Errors containing fixes are automatically applied. /// Errors containing fixes are automatically applied.
void handleErrors(const std::vector<ClangTidyError> &Errors, bool Fix); void handleErrors(const std::vector<ClangTidyError> &Errors, bool Fix,
unsigned &WarningsAsErrorsCount);
/// \brief Serializes replacements into YAML and writes them to the specified /// \brief Serializes replacements into YAML and writes them to the specified
/// output stream. /// output stream.

View File

@ -115,8 +115,10 @@ ClangTidyMessage::ClangTidyMessage(StringRef Message,
} }
ClangTidyError::ClangTidyError(StringRef CheckName, ClangTidyError::ClangTidyError(StringRef CheckName,
ClangTidyError::Level DiagLevel) ClangTidyError::Level DiagLevel,
: CheckName(CheckName), DiagLevel(DiagLevel) {} bool IsWarningAsError)
: CheckName(CheckName), DiagLevel(DiagLevel),
IsWarningAsError(IsWarningAsError) {}
// Returns true if GlobList starts with the negative indicator ('-'), removes it // Returns true if GlobList starts with the negative indicator ('-'), removes it
// from the GlobList. // from the GlobList.
@ -204,6 +206,7 @@ void ClangTidyContext::setCurrentFile(StringRef File) {
CurrentFile = File; CurrentFile = File;
CurrentOptions = getOptionsForFile(CurrentFile); CurrentOptions = getOptionsForFile(CurrentFile);
CheckFilter.reset(new GlobList(*getOptions().Checks)); CheckFilter.reset(new GlobList(*getOptions().Checks));
WarningAsErrorFilter.reset(new GlobList(*getOptions().WarningsAsErrors));
} }
void ClangTidyContext::setASTContext(ASTContext *Context) { void ClangTidyContext::setASTContext(ASTContext *Context) {
@ -237,6 +240,11 @@ bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
return CheckFilter->contains(CheckName); return CheckFilter->contains(CheckName);
} }
GlobList &ClangTidyContext::getWarningAsErrorFilter() {
assert(WarningAsErrorFilter != nullptr);
return *WarningAsErrorFilter;
}
/// \brief Store a \c ClangTidyError. /// \brief Store a \c ClangTidyError.
void ClangTidyContext::storeError(const ClangTidyError &Error) { void ClangTidyContext::storeError(const ClangTidyError &Error) {
Errors.push_back(Error); Errors.push_back(Error);
@ -324,7 +332,10 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
LastErrorRelatesToUserCode = true; LastErrorRelatesToUserCode = true;
LastErrorPassesLineFilter = true; LastErrorPassesLineFilter = true;
} }
Errors.push_back(ClangTidyError(CheckName, Level)); bool IsWarningAsError =
DiagLevel == DiagnosticsEngine::Warning &&
Context.getWarningAsErrorFilter().contains(CheckName);
Errors.push_back(ClangTidyError(CheckName, Level, IsWarningAsError));
} }
// FIXME: Provide correct LangOptions for each file. // FIXME: Provide correct LangOptions for each file.

View File

@ -57,7 +57,7 @@ struct ClangTidyError {
Error = DiagnosticsEngine::Error Error = DiagnosticsEngine::Error
}; };
ClangTidyError(StringRef CheckName, Level DiagLevel); ClangTidyError(StringRef CheckName, Level DiagLevel, bool IsWarningAsError);
std::string CheckName; std::string CheckName;
ClangTidyMessage Message; ClangTidyMessage Message;
@ -65,6 +65,7 @@ struct ClangTidyError {
SmallVector<ClangTidyMessage, 1> Notes; SmallVector<ClangTidyMessage, 1> Notes;
Level DiagLevel; Level DiagLevel;
bool IsWarningAsError;
}; };
/// \brief Read-only set of strings represented as a list of positive and /// \brief Read-only set of strings represented as a list of positive and
@ -164,6 +165,10 @@ public:
/// \brief Returns true if the check name is enabled for the \c CurrentFile. /// \brief Returns true if the check name is enabled for the \c CurrentFile.
bool isCheckEnabled(StringRef CheckName) const; bool isCheckEnabled(StringRef CheckName) const;
/// \brief Returns check filter for the \c CurrentFile which
/// selects checks for upgrade to error.
GlobList &getWarningAsErrorFilter();
/// \brief Returns global options. /// \brief Returns global options.
const ClangTidyGlobalOptions &getGlobalOptions() const; const ClangTidyGlobalOptions &getGlobalOptions() const;
@ -211,6 +216,7 @@ private:
std::string CurrentFile; std::string CurrentFile;
ClangTidyOptions CurrentOptions; ClangTidyOptions CurrentOptions;
std::unique_ptr<GlobList> CheckFilter; std::unique_ptr<GlobList> CheckFilter;
std::unique_ptr<GlobList> WarningAsErrorFilter;
LangOptions LangOpts; LangOptions LangOpts;

View File

@ -85,6 +85,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts( MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(
IO, Options.CheckOptions); IO, Options.CheckOptions);
IO.mapOptional("Checks", Options.Checks); IO.mapOptional("Checks", Options.Checks);
IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors);
IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors); IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors);
IO.mapOptional("User", Options.User); IO.mapOptional("User", Options.User);
@ -103,6 +104,7 @@ namespace tidy {
ClangTidyOptions ClangTidyOptions::getDefaults() { ClangTidyOptions ClangTidyOptions::getDefaults() {
ClangTidyOptions Options; ClangTidyOptions Options;
Options.Checks = ""; Options.Checks = "";
Options.WarningsAsErrors = "";
Options.HeaderFilterRegex = ""; Options.HeaderFilterRegex = "";
Options.SystemHeaders = false; Options.SystemHeaders = false;
Options.AnalyzeTemporaryDtors = false; Options.AnalyzeTemporaryDtors = false;
@ -123,6 +125,12 @@ ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const {
Result.Checks = Result.Checks =
(Result.Checks && !Result.Checks->empty() ? *Result.Checks + "," : "") + (Result.Checks && !Result.Checks->empty() ? *Result.Checks + "," : "") +
*Other.Checks; *Other.Checks;
if (Other.WarningsAsErrors)
Result.WarningsAsErrors =
(Result.WarningsAsErrors && !Result.WarningsAsErrors->empty()
? *Result.WarningsAsErrors + ","
: "") +
*Other.WarningsAsErrors;
if (Other.HeaderFilterRegex) if (Other.HeaderFilterRegex)
Result.HeaderFilterRegex = Other.HeaderFilterRegex; Result.HeaderFilterRegex = Other.HeaderFilterRegex;

View File

@ -62,6 +62,9 @@ struct ClangTidyOptions {
/// \brief Checks filter. /// \brief Checks filter.
llvm::Optional<std::string> Checks; llvm::Optional<std::string> Checks;
/// \brief WarningsAsErrors filter.
llvm::Optional<std::string> WarningsAsErrors;
/// \brief Output warnings from headers matching this filter. Warnings from /// \brief Output warnings from headers matching this filter. Warnings from
/// main files will always be displayed. /// main files will always be displayed.
llvm::Optional<std::string> HeaderFilterRegex; llvm::Optional<std::string> HeaderFilterRegex;

View File

@ -64,15 +64,21 @@ Checks("checks", cl::desc("Comma-separated list of globs with optional '-'\n"
cl::init(""), cl::cat(ClangTidyCategory)); cl::init(""), cl::cat(ClangTidyCategory));
static cl::opt<std::string> static cl::opt<std::string>
HeaderFilter("header-filter", WarningsAsErrors("warnings-as-errors",
cl::desc("Regular expression matching the names of the\n" cl::desc("Upgrades warnings to errors. "
"headers to output diagnostics from. Diagnostics\n" "Same format as '-checks'."),
"from the main file of each translation unit are\n" cl::init(""), cl::cat(ClangTidyCategory));
"always displayed.\n"
"Can be used together with -line-filter.\n" static cl::opt<std::string>
"This option overrides the value read from a\n" HeaderFilter("header-filter",
".clang-tidy file."), cl::desc("Regular expression matching the names of the\n"
cl::init(""), cl::cat(ClangTidyCategory)); "headers to output diagnostics from. Diagnostics\n"
"from the main file of each translation unit are\n"
"always displayed.\n"
"Can be used together with -line-filter.\n"
"This option overrides the value read from a\n"
".clang-tidy file."),
cl::init(""), cl::cat(ClangTidyCategory));
static cl::opt<bool> static cl::opt<bool>
SystemHeaders("system-headers", SystemHeaders("system-headers",
@ -227,6 +233,7 @@ static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider() {
ClangTidyOptions DefaultOptions; ClangTidyOptions DefaultOptions;
DefaultOptions.Checks = DefaultChecks; DefaultOptions.Checks = DefaultChecks;
DefaultOptions.WarningsAsErrors = "";
DefaultOptions.HeaderFilterRegex = HeaderFilter; DefaultOptions.HeaderFilterRegex = HeaderFilter;
DefaultOptions.SystemHeaders = SystemHeaders; DefaultOptions.SystemHeaders = SystemHeaders;
DefaultOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; DefaultOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors;
@ -238,6 +245,8 @@ static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider() {
ClangTidyOptions OverrideOptions; ClangTidyOptions OverrideOptions;
if (Checks.getNumOccurrences() > 0) if (Checks.getNumOccurrences() > 0)
OverrideOptions.Checks = Checks; OverrideOptions.Checks = Checks;
if (WarningsAsErrors.getNumOccurrences() > 0)
OverrideOptions.WarningsAsErrors = WarningsAsErrors;
if (HeaderFilter.getNumOccurrences() > 0) if (HeaderFilter.getNumOccurrences() > 0)
OverrideOptions.HeaderFilterRegex = HeaderFilter; OverrideOptions.HeaderFilterRegex = HeaderFilter;
if (SystemHeaders.getNumOccurrences() > 0) if (SystemHeaders.getNumOccurrences() > 0)
@ -322,8 +331,10 @@ static int clangTidyMain(int argc, const char **argv) {
const bool DisableFixes = Fix && FoundErrors && !FixErrors; const bool DisableFixes = Fix && FoundErrors && !FixErrors;
unsigned WErrorCount = 0;
// -fix-errors implies -fix. // -fix-errors implies -fix.
handleErrors(Errors, (FixErrors || Fix) && !DisableFixes); handleErrors(Errors, (FixErrors || Fix) && !DisableFixes, WErrorCount);
if (!ExportFixes.empty() && !Errors.empty()) { if (!ExportFixes.empty() && !Errors.empty()) {
std::error_code EC; std::error_code EC;
@ -344,6 +355,13 @@ static int clangTidyMain(int argc, const char **argv) {
if (EnableCheckProfile) if (EnableCheckProfile)
printProfileData(Profile, llvm::errs()); printProfileData(Profile, llvm::errs());
if (WErrorCount) {
StringRef Plural = WErrorCount == 1 ? "" : "s";
llvm::errs() << WErrorCount << " warning" << Plural << " treated as error"
<< Plural << "\n";
return WErrorCount;
}
return 0; return 0;
} }

View File

@ -68,7 +68,9 @@ Clang diagnostics are treated in a similar way as check diagnostics. Clang
diagnostics are displayed by clang-tidy and can be filtered out using diagnostics are displayed by clang-tidy and can be filtered out using
``-checks=`` option. However, the ``-checks=`` option does not affect ``-checks=`` option. However, the ``-checks=`` option does not affect
compilation arguments, so it can not turn on Clang warnings which are not compilation arguments, so it can not turn on Clang warnings which are not
already turned on in build configuration. already turned on in build configuration. The ``-warnings-as-errors=`` option
upgrades any warnings emitted under the ``-checks=`` flag to errors (but it
does not enable any checks itself).
Clang diagnostics have check names starting with ``clang-diagnostic-``. Clang diagnostics have check names starting with ``clang-diagnostic-``.
Diagnostics which have a corresponding warning option, are named Diagnostics which have a corresponding warning option, are named
@ -150,6 +152,7 @@ An overview of all the command-line options:
-checks=* to list all available checks. -checks=* to list all available checks.
-p=<string> - Build path -p=<string> - Build path
-system-headers - Display the errors from system headers. -system-headers - Display the errors from system headers.
-warnings-as-errors=<string> - Upgrades warnings to errors. Same format as '-checks'.
-p <build-path> is used to read a compile command database. -p <build-path> is used to read a compile command database.

View File

@ -0,0 +1,15 @@
// RUN: clang-tidy %s -checks='-*,llvm-namespace-comment' -- 2>&1 | FileCheck %s --check-prefix=CHECK-WARN
// RUN: not clang-tidy %s -checks='-*,llvm-namespace-comment' -warnings-as-errors='llvm-namespace-comment' -- 2>&1 | FileCheck %s --check-prefix=CHECK-WERR
namespace j {
}
// CHECK-WARN: warning: namespace 'j' not terminated with a closing comment [llvm-namespace-comment]
// CHECK-WERR: error: namespace 'j' not terminated with a closing comment [llvm-namespace-comment,-warnings-as-errors]
namespace k {
}
// CHECK-WARN: warning: namespace 'k' not terminated with a closing comment [llvm-namespace-comment]
// CHECK-WERR: error: namespace 'k' not terminated with a closing comment [llvm-namespace-comment,-warnings-as-errors]
// CHECK-WARN-NOT: treated as
// CHECK-WERR: 2 warnings treated as errors

View File

@ -0,0 +1,10 @@
// RUN: clang-tidy %s -checks='-*,llvm-namespace-comment' -- 2>&1 | FileCheck %s --check-prefix=CHECK-WARN
// RUN: not clang-tidy %s -checks='-*,llvm-namespace-comment' -warnings-as-errors='llvm-namespace-comment' -- 2>&1 | FileCheck %s --check-prefix=CHECK-WERR
namespace i {
}
// CHECK-WARN: warning: namespace 'i' not terminated with a closing comment [llvm-namespace-comment]
// CHECK-WERR: error: namespace 'i' not terminated with a closing comment [llvm-namespace-comment,-warnings-as-errors]
// CHECK-WARN-NOT: treated as
// CHECK-WERR: 1 warning treated as error