[clang-tools-extra] c0687e1 - Add support for `NOLINTBEGIN` ... `NOLINTEND` comments
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 28 04:53:30 PDT 2021
Author: Salman Javed
Date: 2021-09-28T07:53:23-04:00
New Revision: c0687e1984a82925918c874b7bb68ad34c32aed0
URL: https://github.com/llvm/llvm-project/commit/c0687e1984a82925918c874b7bb68ad34c32aed0
DIFF: https://github.com/llvm/llvm-project/commit/c0687e1984a82925918c874b7bb68ad34c32aed0.diff
LOG: Add support for `NOLINTBEGIN` ... `NOLINTEND` comments
Add support for NOLINTBEGIN ... NOLINTEND comments to suppress
clang-tidy warnings over multiple lines. All lines between the "begin"
and "end" markers are suppressed.
Example:
// NOLINTBEGIN(some-check)
<Code with warnings to be suppressed, line 1>
<Code with warnings to be suppressed, line 2>
<Code with warnings to be suppressed, line 3>
// NOLINTEND(some-check)
Follows similar syntax as the NOLINT and NOLINTNEXTLINE comments
that are already implemented, i.e. allows multiple checks to be provided
in parentheses; suppresses all checks if the parentheses are omitted,
etc.
If the comments are misused, e.g. using a NOLINTBEGIN but not
terminating it with a NOLINTEND, a clang-tidy-nolint diagnostic
message pointing to the misuse is generated.
As part of implementing this feature, the following bugs were fixed in
existing code:
IsNOLINTFound(): IsNOLINTFound("NOLINT", Str) returns true when Str is
"NOLINTNEXTLINE". This is because the textual search finds NOLINT as
the stem of NOLINTNEXTLINE.
LineIsMarkedWithNOLINT(): NOLINTNEXTLINEs on the very first line of a
file are ignored. This is due to rsplit('\n\').second returning a blank
string when there are no more newline chars to split on.
Added:
clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/error_in_include.inc
clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/nolint_in_include.inc
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-at-eof.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-global-end-specific.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-specific-end-global.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-without-end.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-at-sof.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-without-begin.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-check-names.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-delims.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-typo-in-check-name.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend.cpp
Modified:
clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
clang-tools-extra/docs/ReleaseNotes.rst
clang-tools-extra/docs/clang-tidy/index.rst
clang-tools-extra/test/clang-tidy/infrastructure/nolint.cpp
clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index 1457f145f552c..1aa093a2e8db2 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -23,6 +23,7 @@
#include "clang/AST/Attr.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/DiagnosticRenderer.h"
#include "clang/Lex/Lexer.h"
@@ -202,6 +203,17 @@ DiagnosticBuilder ClangTidyContext::diag(
return DiagEngine->Report(ID);
}
+DiagnosticBuilder ClangTidyContext::diag(const ClangTidyError &Error) {
+ SourceManager &SM = DiagEngine->getSourceManager();
+ llvm::ErrorOr<const FileEntry *> File =
+ SM.getFileManager().getFile(Error.Message.FilePath);
+ FileID ID = SM.getOrCreateFileID(*File, SrcMgr::C_User);
+ SourceLocation FileStartLoc = SM.getLocForStartOfFile(ID);
+ SourceLocation Loc = FileStartLoc.getLocWithOffset(Error.Message.FileOffset);
+ return diag(Error.DiagnosticName, Loc, Error.Message.Message,
+ static_cast<DiagnosticIDs::Level>(Error.DiagLevel));
+}
+
DiagnosticBuilder ClangTidyContext::configurationDiag(
StringRef Message,
DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
@@ -307,14 +319,26 @@ void ClangTidyDiagnosticConsumer::finalizeLastError() {
LastErrorPassesLineFilter = false;
}
-static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
- unsigned DiagID, const ClangTidyContext &Context) {
- const size_t NolintIndex = Line.find(NolintDirectiveText);
+static bool isNOLINTFound(StringRef NolintDirectiveText, StringRef CheckName,
+ StringRef Line, size_t *FoundNolintIndex = nullptr,
+ bool *SuppressionIsSpecific = nullptr) {
+ if (FoundNolintIndex)
+ *FoundNolintIndex = StringRef::npos;
+ if (SuppressionIsSpecific)
+ *SuppressionIsSpecific = false;
+
+ size_t NolintIndex = Line.find(NolintDirectiveText);
if (NolintIndex == StringRef::npos)
return false;
size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
- // Check if the specific checks are specified in brackets.
+ if (BracketIndex < Line.size() && isalnum(Line[BracketIndex])) {
+ // Reject this search result, otherwise it will cause false positives when
+ // NOLINT is found as a substring of NOLINT(NEXTLINE/BEGIN/END).
+ return false;
+ }
+
+ // Check if specific checks are specified in brackets.
if (BracketIndex < Line.size() && Line[BracketIndex] == '(') {
++BracketIndex;
const size_t BracketEndIndex = Line.find(')', BracketIndex);
@@ -323,16 +347,22 @@ static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
// Allow disabling all the checks with "*".
if (ChecksStr != "*") {
- std::string CheckName = Context.getCheckName(DiagID);
// Allow specifying a few check names, delimited with comma.
SmallVector<StringRef, 1> Checks;
ChecksStr.split(Checks, ',', -1, false);
llvm::transform(Checks, Checks.begin(),
[](StringRef S) { return S.trim(); });
- return llvm::find(Checks, CheckName) != Checks.end();
+ if (llvm::find(Checks, CheckName) == Checks.end())
+ return false;
+ if (SuppressionIsSpecific)
+ *SuppressionIsSpecific = true;
}
}
}
+
+ if (FoundNolintIndex)
+ *FoundNolintIndex = NolintIndex;
+
return true;
}
@@ -342,34 +372,154 @@ static llvm::Optional<StringRef> getBuffer(const SourceManager &SM, FileID File,
: SM.getBufferDataIfLoaded(File);
}
-static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc,
- unsigned DiagID,
- const ClangTidyContext &Context,
- bool AllowIO) {
+static ClangTidyError createNolintError(const ClangTidyContext &Context,
+ const SourceManager &SM,
+ SourceLocation Loc,
+ bool IsNolintBegin) {
+ ClangTidyError Error("clang-tidy-nolint", ClangTidyError::Error,
+ Context.getCurrentBuildDirectory(), false);
+ StringRef Message =
+ IsNolintBegin
+ ? "unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINTEND' "
+ "comment"
+ : "unmatched 'NOLINTEND' comment without a previous 'NOLINTBEGIN' "
+ "comment";
+ Error.Message = tooling::DiagnosticMessage(Message, SM, Loc);
+ return Error;
+}
+
+static Optional<ClangTidyError>
+tallyNolintBegins(const ClangTidyContext &Context, const SourceManager &SM,
+ StringRef CheckName, SmallVector<StringRef> Lines,
+ SourceLocation LinesLoc,
+ SmallVector<SourceLocation> &SpecificNolintBegins,
+ SmallVector<SourceLocation> &GlobalNolintBegins) {
+ // Keep a running total of how many NOLINT(BEGIN...END) blocks are active.
+ size_t NolintIndex;
+ bool SuppressionIsSpecific;
+ auto List = [&]() -> SmallVector<SourceLocation> * {
+ return SuppressionIsSpecific ? &SpecificNolintBegins : &GlobalNolintBegins;
+ };
+ for (const auto &Line : Lines) {
+ if (isNOLINTFound("NOLINTBEGIN", CheckName, Line, &NolintIndex,
+ &SuppressionIsSpecific)) {
+ // Check if a new block is being started.
+ List()->emplace_back(LinesLoc.getLocWithOffset(NolintIndex));
+ } else if (isNOLINTFound("NOLINTEND", CheckName, Line, &NolintIndex,
+ &SuppressionIsSpecific)) {
+ // Check if the previous block is being closed.
+ if (!List()->empty()) {
+ List()->pop_back();
+ } else {
+ // Trying to close a nonexistent block. Return a diagnostic about this
+ // misuse that can be displayed along with the original clang-tidy check
+ // that the user was attempting to suppress.
+ return createNolintError(Context, SM,
+ LinesLoc.getLocWithOffset(NolintIndex), false);
+ }
+ }
+ // Advance source location to the next line.
+ LinesLoc = LinesLoc.getLocWithOffset(Line.size() + sizeof('\n'));
+ }
+ return None; // All NOLINT(BEGIN/END) use has been consistent so far.
+}
+
+static bool
+lineIsWithinNolintBegin(const ClangTidyContext &Context,
+ SmallVectorImpl<ClangTidyError> &SuppressionErrors,
+ const SourceManager &SM, SourceLocation Loc,
+ StringRef CheckName, StringRef TextBeforeDiag,
+ StringRef TextAfterDiag) {
+ Loc = SM.getExpansionRange(Loc).getBegin();
+ SourceLocation FileStartLoc = SM.getLocForStartOfFile(SM.getFileID(Loc));
+
+ // Check if there's an open NOLINT(BEGIN...END) block on the previous lines.
+ SmallVector<StringRef> PrevLines;
+ TextBeforeDiag.split(PrevLines, '\n');
+ SmallVector<SourceLocation> SpecificNolintBegins;
+ SmallVector<SourceLocation> GlobalNolintBegins;
+ auto Error =
+ tallyNolintBegins(Context, SM, CheckName, PrevLines, FileStartLoc,
+ SpecificNolintBegins, GlobalNolintBegins);
+ if (Error) {
+ SuppressionErrors.emplace_back(Error.getValue());
+ return false;
+ }
+ bool WithinNolintBegin =
+ !SpecificNolintBegins.empty() || !GlobalNolintBegins.empty();
+
+ // Check that every block is terminated correctly on the following lines.
+ SmallVector<StringRef> FollowingLines;
+ TextAfterDiag.split(FollowingLines, '\n');
+ Error = tallyNolintBegins(Context, SM, CheckName, FollowingLines, Loc,
+ SpecificNolintBegins, GlobalNolintBegins);
+ if (Error) {
+ SuppressionErrors.emplace_back(Error.getValue());
+ return false;
+ }
+
+ // The following blocks were never closed. Return diagnostics for each
+ // instance that can be displayed along with the original clang-tidy check
+ // that the user was attempting to suppress.
+ for (const auto NolintBegin : SpecificNolintBegins) {
+ auto Error = createNolintError(Context, SM, NolintBegin, true);
+ SuppressionErrors.emplace_back(Error);
+ }
+ for (const auto NolintBegin : GlobalNolintBegins) {
+ auto Error = createNolintError(Context, SM, NolintBegin, true);
+ SuppressionErrors.emplace_back(Error);
+ }
+
+ return WithinNolintBegin && SuppressionErrors.empty();
+}
+
+static bool
+lineIsMarkedWithNOLINT(const ClangTidyContext &Context,
+ SmallVectorImpl<ClangTidyError> &SuppressionErrors,
+ bool AllowIO, const SourceManager &SM,
+ SourceLocation Loc, StringRef CheckName) {
+ // Get source code for this location.
FileID File;
unsigned Offset;
std::tie(File, Offset) = SM.getDecomposedSpellingLoc(Loc);
- llvm::Optional<StringRef> Buffer = getBuffer(SM, File, AllowIO);
+ Optional<StringRef> Buffer = getBuffer(SM, File, AllowIO);
if (!Buffer)
return false;
// Check if there's a NOLINT on this line.
- StringRef RestOfLine = Buffer->substr(Offset).split('\n').first;
- if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context))
+ StringRef TextAfterDiag = Buffer->substr(Offset);
+ StringRef RestOfThisLine, FollowingLines;
+ std::tie(RestOfThisLine, FollowingLines) = TextAfterDiag.split('\n');
+ if (isNOLINTFound("NOLINT", CheckName, RestOfThisLine))
return true;
// Check if there's a NOLINTNEXTLINE on the previous line.
- StringRef PrevLine =
- Buffer->substr(0, Offset).rsplit('\n').first.rsplit('\n').second;
- return IsNOLINTFound("NOLINTNEXTLINE", PrevLine, DiagID, Context);
+ StringRef TextBeforeDiag = Buffer->substr(0, Offset);
+ size_t LastNewLinePos = TextBeforeDiag.rfind('\n');
+ StringRef PrevLines = (LastNewLinePos == StringRef::npos)
+ ? StringRef()
+ : TextBeforeDiag.slice(0, LastNewLinePos);
+ LastNewLinePos = PrevLines.rfind('\n');
+ StringRef PrevLine = (LastNewLinePos == StringRef::npos)
+ ? PrevLines
+ : PrevLines.substr(LastNewLinePos + 1);
+ if (isNOLINTFound("NOLINTNEXTLINE", CheckName, PrevLine))
+ return true;
+
+ // Check if this line is within a NOLINT(BEGIN...END) block.
+ return lineIsWithinNolintBegin(Context, SuppressionErrors, SM, Loc, CheckName,
+ TextBeforeDiag, TextAfterDiag);
}
-static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM,
- SourceLocation Loc, unsigned DiagID,
- const ClangTidyContext &Context,
- bool AllowIO) {
+static bool lineIsMarkedWithNOLINTinMacro(
+ const Diagnostic &Info, const ClangTidyContext &Context,
+ SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO) {
+ const SourceManager &SM = Info.getSourceManager();
+ SourceLocation Loc = Info.getLocation();
+ std::string CheckName = Context.getCheckName(Info.getID());
while (true) {
- if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context, AllowIO))
+ if (lineIsMarkedWithNOLINT(Context, SuppressionErrors, AllowIO, SM, Loc,
+ CheckName))
return true;
if (!Loc.isMacroID())
return false;
@@ -384,12 +534,22 @@ namespace tidy {
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info, ClangTidyContext &Context,
bool AllowIO) {
+ SmallVector<ClangTidyError, 1> Unused;
+ bool ShouldSuppress =
+ shouldSuppressDiagnostic(DiagLevel, Info, Context, Unused, AllowIO);
+ assert(Unused.empty());
+ return ShouldSuppress;
+}
+
+bool shouldSuppressDiagnostic(
+ DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
+ ClangTidyContext &Context,
+ SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO) {
return Info.getLocation().isValid() &&
DiagLevel != DiagnosticsEngine::Error &&
DiagLevel != DiagnosticsEngine::Fatal &&
- LineIsMarkedWithNOLINTinMacro(Info.getSourceManager(),
- Info.getLocation(), Info.getID(),
- Context, AllowIO);
+ lineIsMarkedWithNOLINTinMacro(Info, Context, SuppressionErrors,
+ AllowIO);
}
const llvm::StringMap<tooling::Replacements> *
@@ -418,7 +578,8 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
return;
- if (shouldSuppressDiagnostic(DiagLevel, Info, Context)) {
+ SmallVector<ClangTidyError, 1> SuppressionErrors;
+ if (shouldSuppressDiagnostic(DiagLevel, Info, Context, SuppressionErrors)) {
++Context.Stats.ErrorsIgnoredNOLINT;
// Ignored a warning, should ignore related notes as well
LastErrorWasIgnored = true;
@@ -492,6 +653,10 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
if (Info.hasSourceManager())
checkFilters(Info.getLocation(), Info.getSourceManager());
+
+ Context.DiagEngine->Clear();
+ for (const auto &Error : SuppressionErrors)
+ Context.diag(Error);
}
bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
index 13372cc626b9e..84925e81dc08e 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -99,6 +99,8 @@ class ClangTidyContext {
DiagnosticBuilder diag(StringRef CheckName, StringRef Message,
DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
+ DiagnosticBuilder diag(const ClangTidyError &Error);
+
/// Report any errors to do with reading the configuration using this method.
DiagnosticBuilder
configurationDiag(StringRef Message,
@@ -165,7 +167,7 @@ class ClangTidyContext {
}
/// Returns build directory of the current translation unit.
- const std::string &getCurrentBuildDirectory() {
+ const std::string &getCurrentBuildDirectory() const {
return CurrentBuildDirectory;
}
@@ -217,15 +219,24 @@ class ClangTidyContext {
/// This is exposed so that other tools that present clang-tidy diagnostics
/// (such as clangd) can respect the same suppression rules as clang-tidy.
/// This does not handle suppression of notes following a suppressed diagnostic;
-/// that is left to the caller is it requires maintaining state in between calls
+/// that is left to the caller as it requires maintaining state in between calls
/// to this function.
/// If `AllowIO` is false, the function does not attempt to read source files
/// from disk which are not already mapped into memory; such files are treated
/// as not containing a suppression comment.
+/// If suppression is not possible due to improper use of "NOLINT" comments -
+/// for example, the use of a "NOLINTBEGIN" comment that is not followed by a
+/// "NOLINTEND" comment - a diagnostic regarding the improper use is returned
+/// via the output argument `SuppressionErrors`.
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info, ClangTidyContext &Context,
bool AllowIO = true);
+bool shouldSuppressDiagnostic(
+ DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
+ ClangTidyContext &Context,
+ SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO = true);
+
/// Gets the Fix attached to \p Diagnostic.
/// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check
/// to see if exactly one note has a Fix and return it. Otherwise return
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 79048ce17f52c..3257a3d187e6f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -67,7 +67,8 @@ The improvements are...
Improvements to clang-tidy
--------------------------
-The improvements are...
+- Added support for `NOLINTBEGIN` ... `NOLINTEND` comments to suppress
+ Clang-Tidy warnings over multiple lines.
New checks
^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index 63b895b7c0114..e3c454554eab8 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -295,17 +295,19 @@ using explicit casts, etc.
If a specific suppression mechanism is not available for a certain warning, or
its use is not desired for some reason, :program:`clang-tidy` has a generic
-mechanism to suppress diagnostics using ``NOLINT`` or ``NOLINTNEXTLINE``
-comments.
+mechanism to suppress diagnostics using ``NOLINT``, ``NOLINTNEXTLINE``, and
+``NOLINTBEGIN`` ... ``NOLINTEND`` comments.
The ``NOLINT`` comment instructs :program:`clang-tidy` to ignore warnings on the
*same line* (it doesn't apply to a function, a block of code or any other
-language construct, it applies to the line of code it is on). If introducing the
-comment in the same line would change the formatting in undesired way, the
+language construct; it applies to the line of code it is on). If introducing the
+comment in the same line would change the formatting in an undesired way, the
``NOLINTNEXTLINE`` comment allows to suppress clang-tidy warnings on the *next
-line*.
+line*. The ``NOLINTBEGIN`` and ``NOLINTEND`` comments allow suppressing
+clang-tidy warnings on *multiple lines* (affecting all lines between the two
+comments).
-Both comments can be followed by an optional list of check names in parentheses
+All comments can be followed by an optional list of check names in parentheses
(see below for the formal syntax).
For example:
@@ -325,9 +327,16 @@ For example:
// Silence only the specified diagnostics for the next line
// NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int)
Foo(bool param);
+
+ // Silence only the specified checks for all lines between the BEGIN and END
+ // NOLINTBEGIN(google-explicit-constructor, google-runtime-int)
+ Foo(short param);
+ Foo(long param);
+ // NOLINTEND(google-explicit-constructor, google-runtime-int)
};
-The formal syntax of ``NOLINT``/``NOLINTNEXTLINE`` is the following:
+The formal syntax of ``NOLINT``, ``NOLINTNEXTLINE``, and ``NOLINTBEGIN`` ...
+``NOLINTEND`` is the following:
.. parsed-literal::
@@ -345,11 +354,22 @@ The formal syntax of ``NOLINT``/``NOLINTNEXTLINE`` is the following:
lint-command:
**NOLINT**
**NOLINTNEXTLINE**
+ **NOLINTBEGIN**
+ **NOLINTEND**
-Note that whitespaces between ``NOLINT``/``NOLINTNEXTLINE`` and the opening
+Note that whitespaces between
+``NOLINT``/``NOLINTNEXTLINE``/``NOLINTBEGIN``/``NOLINTEND`` and the opening
parenthesis are not allowed (in this case the comment will be treated just as
-``NOLINT``/``NOLINTNEXTLINE``), whereas in check names list (inside the
-parenthesis) whitespaces can be used and will be ignored.
+``NOLINT``/``NOLINTNEXTLINE``/``NOLINTBEGIN``/``NOLINTEND``), whereas in the
+check names list (inside the parentheses), whitespaces can be used and will be
+ignored.
+
+All ``NOLINTBEGIN`` comments must be paired by an equal number of ``NOLINTEND``
+comments. Moreover, a pair of comments must have matching arguments -- for
+example, ``NOLINTBEGIN(check-name)`` can be paired with
+``NOLINTEND(check-name)`` but not with ``NOLINTEND`` `(zero arguments)`.
+:program:`clang-tidy` will generate a ``clang-tidy-nolint`` error diagnostic if
+any ``NOLINTBEGIN``/``NOLINTEND`` comment violates these requirements.
.. _LibTooling: https://clang.llvm.org/docs/LibTooling.html
.. _How To Setup Tooling For LLVM: https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/error_in_include.inc b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/error_in_include.inc
new file mode 100644
index 0000000000000..950b48d2815a4
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/error_in_include.inc
@@ -0,0 +1 @@
+class G { G(int i); };
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/nolint_in_include.inc b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/nolint_in_include.inc
new file mode 100644
index 0000000000000..7c3dfefe247a5
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/nolintbeginend/nolint_in_include.inc
@@ -0,0 +1,3 @@
+// NOLINTBEGIN
+class H { H(int i); };
+// NOLINTEND
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolint.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolint.cpp
index a2d2c10af4e97..fc17f12855127 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/nolint.cpp
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolint.cpp
@@ -27,6 +27,9 @@ class C4 { C4(int i); }; // NOLINT(some-check, google-explicit-constructor)
class C5 { C5(int i); }; // NOLINT without-brackets-skip-all, another-check
+class C6 { C6(int i); }; // NOLINTNEXTLINE doesn't get misconstrued as a NOLINT
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: single-argument constructors must be marked explicit
+
void f() {
int i;
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: unused variable 'i' [clang-diagnostic-unused-variable]
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-at-eof.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-at-eof.cpp
new file mode 100644
index 0000000000000..0d3dcf381eaba
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-at-eof.cpp
@@ -0,0 +1,12 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
+
+// CHECK: :[[@LINE+8]]:11: warning: single-argument constructors must be marked explicit
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE+5]]:4: error: unmatched 'NOLIN
+// CHECK: TBEGIN' comment without a subsequent 'NOLIN
+// CHECK: TEND' comment [clang-tidy-nolint]
+
+class A { A(int i); };
+// NOLINTBEGIN
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-global-end-specific.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-global-end-specific.cpp
new file mode 100644
index 0000000000000..b19d987994cad
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-global-end-specific.cpp
@@ -0,0 +1,13 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
+
+// NOLINTBEGIN
+class A { A(int i); };
+// NOLINTEND(google-explicit-constructor)
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-6]]:11: warning: single-argument constructors must be marked explicit
+// CHECK: :[[@LINE-6]]:4: error: unmatched 'NOLIN
+// CHECK: TEND' comment without a previous 'NOLIN
+// CHECK: TBEGIN' comment [clang-tidy-nolint]
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-specific-end-global.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-specific-end-global.cpp
new file mode 100644
index 0000000000000..67ca4b7aaf0d7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-specific-end-global.cpp
@@ -0,0 +1,13 @@
+// RUN: not clang-tidy %s --checks="-*,google-explicit-constructor" 2>&1 | FileCheck %s
+
+// NOLINTBEGIN(google-explicit-constructor)
+class A { A(int i); };
+// NOLINTEND
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-6]]:11: warning: single-argument constructors must be marked explicit
+// CHECK: :[[@LINE-6]]:4: error: unmatched 'NOLIN
+// CHECK: TEND' comment without a previous 'NOLIN
+// CHECK: TBEGIN' comment [clang-tidy-nolint]
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-without-end.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-without-end.cpp
new file mode 100644
index 0000000000000..2cb84ae59775d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-begin-without-end.cpp
@@ -0,0 +1,12 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
+
+// NOLINTBEGIN
+class A { A(int i); };
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-6]]:4: error: unmatched 'NOLIN
+// CHECK: TBEGIN' comment without a subsequent 'NOLIN
+// CHECK: TEND' comment [clang-tidy-nolint]
+// CHECK: :[[@LINE-8]]:11: warning: single-argument constructors must be marked explicit
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-at-sof.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-at-sof.cpp
new file mode 100644
index 0000000000000..72b8ac9256866
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-at-sof.cpp
@@ -0,0 +1,12 @@
+// NOLINTEND
+class A { A(int i); };
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-6]]:4: error: unmatched 'NOLIN
+// CHECK: TEND' comment without a previous 'NOLIN
+// CHECK: TBEGIN' comment [clang-tidy-nolint]
+// CHECK: :[[@LINE-8]]:11: warning: single-argument constructors must be marked explicit
+
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-without-begin.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-without-begin.cpp
new file mode 100644
index 0000000000000..cea16610823dd
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-end-without-begin.cpp
@@ -0,0 +1,12 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
+
+// NOLINTEND
+class A { A(int i); };
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-6]]:4: error: unmatched 'NOLIN
+// CHECK: TEND' comment without a previous 'NOLIN
+// CHECK: TBEGIN' comment [clang-tidy-nolint]
+// CHECK: :[[@LINE-8]]:11: warning: single-argument constructors must be marked explicit
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-check-names.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-check-names.cpp
new file mode 100644
index 0000000000000..8d7786fb8c712
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-check-names.cpp
@@ -0,0 +1,18 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor,google-readability-casting' 2>&1 | FileCheck %s
+
+// NOLINTBEGIN(google-explicit-constructor)
+class A { A(int i); };
+auto Num = (unsigned int)(-1);
+// NOLINTEND(google-readability-casting)
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-8]]:4: error: unmatched 'NOLIN
+// CHECK: TBEGIN' comment without a subsequent 'NOLIN
+// CHECK: TEND' comment [clang-tidy-nolint]
+// CHECK: :[[@LINE-10]]:11: warning: single-argument constructors must be marked explicit
+// CHECK: :[[@LINE-10]]:12: warning: C-style casts are discouraged; use static_cast
+// CHECK: :[[@LINE-10]]:4: error: unmatched 'NOLIN
+// CHECK: TEND' comment without a previous 'NOLIN
+// CHECK: TBEGIN' comment [clang-tidy-nolint]
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-delims.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-delims.cpp
new file mode 100644
index 0000000000000..7ed5fb820e509
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-mismatched-delims.cpp
@@ -0,0 +1,14 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
+
+// NOLINTBEGIN
+// NOLINTBEGIN
+class A { A(int i); };
+// NOLINTEND
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-8]]:4: error: unmatched 'NOLIN
+// CHECK: TBEGIN' comment without a subsequent 'NOLIN
+// CHECK: TEND' comment [clang-tidy-nolint]
+// CHECK: :[[@LINE-9]]:11: warning: single-argument constructors must be marked explicit
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-typo-in-check-name.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-typo-in-check-name.cpp
new file mode 100644
index 0000000000000..0f2f9994c1fa0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend-typo-in-check-name.cpp
@@ -0,0 +1,13 @@
+// RUN: not clang-tidy %s --checks='-*,google-explicit-constructor' 2>&1 | FileCheck %s
+
+// NOLINTBEGIN(google-explicit-constructor)
+class A { A(int i); };
+// NOLINTEND(google-explicit-constructo) <-- typo: missing 'r'
+
+// Note: the expected output has been split over several lines so that clang-tidy
+// does not see the "no lint" suppression comment and mistakenly assume it
+// is meant for itself.
+// CHECK: :[[@LINE-7]]:4: error: unmatched 'NOLIN
+// CHECK: TBEGIN' comment without a subsequent 'NOLIN
+// CHECK: TEND' comment [clang-tidy-nolint]
+// CHECK: :[[@LINE-9]]:11: warning: single-argument constructors must be marked explicit
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend.cpp
new file mode 100644
index 0000000000000..a57b1e2db4acc
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintbeginend.cpp
@@ -0,0 +1,127 @@
+// RUN: %check_clang_tidy %s google-explicit-constructor %t -- --header-filter=.* -system-headers -- -isystem %S/Inputs/nolintbeginend
+
+class A { A(int i); };
+// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit
+
+// NOLINTBEGIN
+class B1 { B1(int i); };
+// NOLINTEND
+
+// NOLINTBEGIN
+// NOLINTEND
+// NOLINTBEGIN
+class B2 { B2(int i); };
+// NOLINTEND
+
+// NOLINTBEGIN
+// NOLINTBEGIN
+class B3 { B3(int i); };
+// NOLINTEND
+// NOLINTEND
+
+// NOLINTBEGIN
+// NOLINTBEGIN
+// NOLINTEND
+class B4 { B4(int i); };
+// NOLINTEND
+
+// NOLINTBEGIN
+// NOLINTEND
+class B5 { B5(int i); };
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: single-argument constructors must be marked explicit
+
+// NOLINTBEGIN(google-explicit-constructor)
+class C1 { C1(int i); };
+// NOLINTEND(google-explicit-constructor)
+
+// NOLINTBEGIN(*)
+class C2 { C2(int i); };
+// NOLINTEND(*)
+
+// NOLINTBEGIN(some-other-check)
+class C3 { C3(int i); };
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: single-argument constructors must be marked explicit
+// NOLINTEND(some-other-check)
+
+// NOLINTBEGIN(some-other-check, google-explicit-constructor)
+class C4 { C4(int i); };
+// NOLINTEND(some-other-check, google-explicit-constructor)
+
+// NOLINTBEGIN(some-other-check, google-explicit-constructor)
+// NOLINTEND(some-other-check)
+class C5 { C5(int i); };
+// NOLINTEND(google-explicit-constructor)
+
+// NOLINTBEGIN(some-other-check, google-explicit-constructor)
+// NOLINTEND(google-explicit-constructor)
+class C6 { C6(int i); };
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: single-argument constructors must be marked explicit
+// NOLINTEND(some-other-check)
+
+// NOLINTBEGIN(google-explicit-constructor)
+// NOLINTBEGIN(some-other-check)
+class C7 { C7(int i); };
+// NOLINTEND(some-other-check)
+// NOLINTEND(google-explicit-constructor)
+
+// NOLINTBEGIN(google-explicit-constructor)
+// NOLINTBEGIN(some-other-check)
+class C8 { C8(int i); };
+// NOLINTEND(google-explicit-constructor)
+// NOLINTEND(some-other-check)
+
+// NOLINTBEGIN(google-explicit-constructor)
+// NOLINTBEGIN
+class C9 { C9(int i); };
+// NOLINTEND
+// NOLINTEND(google-explicit-constructor)
+
+// NOLINTBEGIN
+// NOLINTBEGIN(google-explicit-constructor)
+class C10 { C10(int i); };
+// NOLINTEND(google-explicit-constructor)
+// NOLINTEND
+
+// NOLINTBEGIN(not-closed-bracket-is-treated-as-skip-all
+class C11 { C11(int i); };
+// NOLINTEND(not-closed-bracket-is-treated-as-skip-all
+
+// NOLINTBEGIN without-brackets-skip-all, another-check
+class C12 { C12(int i); };
+// NOLINTEND without-brackets-skip-all, another-check
+
+#define MACRO(X) class X { X(int i); };
+
+MACRO(D1)
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: single-argument constructors must be marked explicit
+// CHECK-MESSAGES: :[[@LINE-4]]:28: note: expanded from macro 'MACRO
+
+// NOLINTBEGIN
+MACRO(D2)
+// NOLINTEND
+
+#define MACRO_NOARG class E { E(int i); };
+
+// NOLINTBEGIN
+MACRO_NOARG
+// NOLINTEND
+
+#include "error_in_include.inc"
+// CHECK-MESSAGES: error_in_include.inc:1:11: warning: single-argument constructors must be marked explicit
+
+#include "nolint_in_include.inc"
+
+// NOLINTBEGIN
+#define MACRO_WRAPPED_WITH_NO_LINT class I { I(int i); };
+// NOLINTEND
+
+MACRO_WRAPPED_WITH_NO_LINT
+
+#define MACRO_NO_LINT_INSIDE_MACRO \
+ /* NOLINTBEGIN */ \
+ class J { J(int i); }; \
+ /* NOLINTEND */
+
+MACRO_NO_LINT_INSIDE_MACRO
+
+// CHECK-MESSAGES: Suppressed 19 warnings (19 NOLINT).
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline.cpp
index a97928ae0aca9..0370621f7dddd 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline.cpp
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/nolintnextline.cpp
@@ -1,49 +1,51 @@
+// NOLINTNEXTLINE
class A { A(int i); };
+
+class B { B(int i); };
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit
// NOLINTNEXTLINE
-class B { B(int i); };
+class C { C(int i); };
// NOLINTNEXTLINE(for-some-other-check)
-class C { C(int i); };
+class D { D(int i); };
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit
// NOLINTNEXTLINE(*)
-class C1 { C1(int i); };
+class D1 { D1(int i); };
// NOLINTNEXTLINE(not-closed-bracket-is-treated-as-skip-all
-class C2 { C2(int i); };
+class D2 { D2(int i); };
// NOLINTNEXTLINE(google-explicit-constructor)
-class C3 { C3(int i); };
+class D3 { D3(int i); };
// NOLINTNEXTLINE(some-check, google-explicit-constructor)
-class C4 { C4(int i); };
+class D4 { D4(int i); };
// NOLINTNEXTLINE without-brackets-skip-all, another-check
-class C5 { C5(int i); };
-
+class D5 { D5(int i); };
// NOLINTNEXTLINE
-class D { D(int i); };
+class E { E(int i); };
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit
// NOLINTNEXTLINE
//
-class E { E(int i); };
+class F { F(int i); };
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: single-argument constructors must be marked explicit
#define MACRO(X) class X { X(int i); };
-MACRO(F)
+MACRO(G)
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: single-argument constructors must be marked explicit
// NOLINTNEXTLINE
-MACRO(G)
+MACRO(H)
-#define MACRO_NOARG class H { H(int i); };
+#define MACRO_NOARG class I { I(int i); };
// NOLINTNEXTLINE
MACRO_NOARG
-// CHECK-MESSAGES: Suppressed 8 warnings (8 NOLINT)
+// CHECK-MESSAGES: Suppressed 9 warnings (9 NOLINT)
// RUN: %check_clang_tidy %s google-explicit-constructor %t --
More information about the cfe-commits
mailing list