1818#include " ClangTidyDiagnosticConsumer.h"
1919#include " ClangTidyOptions.h"
2020#include " GlobList.h"
21+ #include " NoLintDirectiveHandler.h"
2122#include " clang/AST/ASTContext.h"
2223#include " clang/AST/ASTDiagnostic.h"
2324#include " clang/AST/Attr.h"
@@ -188,7 +189,7 @@ DiagnosticBuilder ClangTidyContext::diag(
188189 return DiagEngine->Report (ID);
189190}
190191
191- DiagnosticBuilder ClangTidyContext::diag (const ClangTidyError &Error) {
192+ DiagnosticBuilder ClangTidyContext::diag (const tooling::Diagnostic &Error) {
192193 SourceManager &SM = DiagEngine->getSourceManager ();
193194 llvm::ErrorOr<const FileEntry *> File =
194195 SM.getFileManager ().getFile (Error.Message .FilePath );
@@ -206,6 +207,15 @@ DiagnosticBuilder ClangTidyContext::configurationDiag(
206207 return diag (" clang-tidy-config" , Message, Level);
207208}
208209
210+ bool ClangTidyContext::shouldSuppressDiagnostic (
211+ DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
212+ SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
213+ bool EnableNoLintBlocks) {
214+ std::string CheckName = getCheckName (Info.getID ());
215+ return NoLintHandler.shouldSuppress (DiagLevel, Info, CheckName, NoLintErrors,
216+ AllowIO, EnableNoLintBlocks);
217+ }
218+
209219void ClangTidyContext::setSourceManager (SourceManager *SourceMgr) {
210220 DiagEngine->setSourceManager (SourceMgr);
211221}
@@ -307,218 +317,9 @@ void ClangTidyDiagnosticConsumer::finalizeLastError() {
307317 LastErrorPassesLineFilter = false ;
308318}
309319
310- static bool isNOLINTFound (StringRef NolintDirectiveText, StringRef CheckName,
311- StringRef Line, size_t *FoundNolintIndex = nullptr ,
312- StringRef *FoundNolintChecksStr = nullptr ) {
313- if (FoundNolintIndex)
314- *FoundNolintIndex = StringRef::npos;
315- if (FoundNolintChecksStr)
316- *FoundNolintChecksStr = StringRef ();
317-
318- size_t NolintIndex = Line.find (NolintDirectiveText);
319- if (NolintIndex == StringRef::npos)
320- return false ;
321-
322- size_t BracketIndex = NolintIndex + NolintDirectiveText.size ();
323- if (BracketIndex < Line.size () && isalnum (Line[BracketIndex])) {
324- // Reject this search result, otherwise it will cause false positives when
325- // NOLINT is found as a substring of NOLINT(NEXTLINE/BEGIN/END).
326- return false ;
327- }
328-
329- // Check if specific checks are specified in brackets.
330- if (BracketIndex < Line.size () && Line[BracketIndex] == ' (' ) {
331- ++BracketIndex;
332- const size_t BracketEndIndex = Line.find (' )' , BracketIndex);
333- if (BracketEndIndex != StringRef::npos) {
334- StringRef ChecksStr =
335- Line.substr (BracketIndex, BracketEndIndex - BracketIndex);
336- if (FoundNolintChecksStr)
337- *FoundNolintChecksStr = ChecksStr;
338- // Allow specifying a few checks with a glob expression, ignoring
339- // negative globs (which would effectively disable the suppression).
340- GlobList Globs (ChecksStr, /* KeepNegativeGlobs=*/ false );
341- if (!Globs.contains (CheckName))
342- return false ;
343- }
344- }
345-
346- if (FoundNolintIndex)
347- *FoundNolintIndex = NolintIndex;
348-
349- return true ;
350- }
351-
352- static llvm::Optional<StringRef> getBuffer (const SourceManager &SM, FileID File,
353- bool AllowIO) {
354- return AllowIO ? SM.getBufferDataOrNone (File)
355- : SM.getBufferDataIfLoaded (File);
356- }
357-
358- static ClangTidyError createNolintError (const ClangTidyContext &Context,
359- const SourceManager &SM,
360- SourceLocation Loc,
361- bool IsNolintBegin) {
362- ClangTidyError Error (" clang-tidy-nolint" , ClangTidyError::Error,
363- Context.getCurrentBuildDirectory (), false );
364- StringRef Message =
365- IsNolintBegin
366- ? (" unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
367- " END' comment" )
368- : (" unmatched 'NOLINTEND' comment without a previous 'NOLINT"
369- " BEGIN' comment" );
370- Error.Message = tooling::DiagnosticMessage (Message, SM, Loc);
371- return Error;
372- }
373-
374- static Optional<ClangTidyError> tallyNolintBegins (
375- const ClangTidyContext &Context, const SourceManager &SM,
376- StringRef CheckName, SmallVector<StringRef> Lines, SourceLocation LinesLoc,
377- SmallVector<std::pair<SourceLocation, StringRef>> &NolintBegins) {
378- // Keep a running total of how many NOLINT(BEGIN...END) blocks are active, as
379- // well as the bracket expression (if any) that was used in the NOLINT
380- // expression.
381- size_t NolintIndex;
382- StringRef NolintChecksStr;
383- for (const auto &Line : Lines) {
384- if (isNOLINTFound (" NOLINTBEGIN" , CheckName, Line, &NolintIndex,
385- &NolintChecksStr)) {
386- // Check if a new block is being started.
387- NolintBegins.emplace_back (std::make_pair (
388- LinesLoc.getLocWithOffset (NolintIndex), NolintChecksStr));
389- } else if (isNOLINTFound (" NOLINTEND" , CheckName, Line, &NolintIndex,
390- &NolintChecksStr)) {
391- // Check if the previous block is being closed.
392- if (!NolintBegins.empty () &&
393- NolintBegins.back ().second == NolintChecksStr) {
394- NolintBegins.pop_back ();
395- } else {
396- // Trying to close a nonexistent block. Return a diagnostic about this
397- // misuse that can be displayed along with the original clang-tidy check
398- // that the user was attempting to suppress.
399- return createNolintError (Context, SM,
400- LinesLoc.getLocWithOffset (NolintIndex), false );
401- }
402- }
403- // Advance source location to the next line.
404- LinesLoc = LinesLoc.getLocWithOffset (Line.size () + sizeof (' \n ' ));
405- }
406- return None; // All NOLINT(BEGIN/END) use has been consistent so far.
407- }
408-
409- static bool
410- lineIsWithinNolintBegin (const ClangTidyContext &Context,
411- SmallVectorImpl<ClangTidyError> &SuppressionErrors,
412- const SourceManager &SM, SourceLocation Loc,
413- StringRef CheckName, StringRef TextBeforeDiag,
414- StringRef TextAfterDiag) {
415- Loc = SM.getExpansionRange (Loc).getBegin ();
416- SourceLocation FileStartLoc = SM.getLocForStartOfFile (SM.getFileID (Loc));
417- SmallVector<std::pair<SourceLocation, StringRef>> NolintBegins;
418-
419- // Check if there's an open NOLINT(BEGIN...END) block on the previous lines.
420- SmallVector<StringRef> PrevLines;
421- TextBeforeDiag.split (PrevLines, ' \n ' );
422- auto Error = tallyNolintBegins (Context, SM, CheckName, PrevLines,
423- FileStartLoc, NolintBegins);
424- if (Error) {
425- SuppressionErrors.emplace_back (Error.getValue ());
426- }
427- bool WithinNolintBegin = !NolintBegins.empty ();
428-
429- // Check that every block is terminated correctly on the following lines.
430- SmallVector<StringRef> FollowingLines;
431- TextAfterDiag.split (FollowingLines, ' \n ' );
432- Error = tallyNolintBegins (Context, SM, CheckName, FollowingLines, Loc,
433- NolintBegins);
434- if (Error) {
435- SuppressionErrors.emplace_back (Error.getValue ());
436- }
437-
438- // The following blocks were never closed. Return diagnostics for each
439- // instance that can be displayed along with the original clang-tidy check
440- // that the user was attempting to suppress.
441- for (const auto &NolintBegin : NolintBegins) {
442- SuppressionErrors.emplace_back (
443- createNolintError (Context, SM, NolintBegin.first , true ));
444- }
445-
446- return WithinNolintBegin && SuppressionErrors.empty ();
447- }
448-
449- static bool
450- lineIsMarkedWithNOLINT (const ClangTidyContext &Context,
451- SmallVectorImpl<ClangTidyError> &SuppressionErrors,
452- bool AllowIO, const SourceManager &SM,
453- SourceLocation Loc, StringRef CheckName,
454- bool EnableNolintBlocks) {
455- // Get source code for this location.
456- FileID File;
457- unsigned Offset;
458- std::tie (File, Offset) = SM.getDecomposedSpellingLoc (Loc);
459- Optional<StringRef> Buffer = getBuffer (SM, File, AllowIO);
460- if (!Buffer)
461- return false ;
462-
463- // Check if there's a NOLINT on this line.
464- StringRef TextAfterDiag = Buffer->substr (Offset);
465- StringRef RestOfThisLine, FollowingLines;
466- std::tie (RestOfThisLine, FollowingLines) = TextAfterDiag.split (' \n ' );
467- if (isNOLINTFound (" NOLINT" , CheckName, RestOfThisLine))
468- return true ;
469-
470- // Check if there's a NOLINTNEXTLINE on the previous line.
471- StringRef TextBeforeDiag = Buffer->substr (0 , Offset);
472- size_t LastNewLinePos = TextBeforeDiag.rfind (' \n ' );
473- StringRef PrevLines = (LastNewLinePos == StringRef::npos)
474- ? StringRef ()
475- : TextBeforeDiag.slice (0 , LastNewLinePos);
476- LastNewLinePos = PrevLines.rfind (' \n ' );
477- StringRef PrevLine = (LastNewLinePos == StringRef::npos)
478- ? PrevLines
479- : PrevLines.substr (LastNewLinePos + 1 );
480- if (isNOLINTFound (" NOLINTNEXTLINE" , CheckName, PrevLine))
481- return true ;
482-
483- // Check if this line is within a NOLINT(BEGIN...END) block.
484- return EnableNolintBlocks &&
485- lineIsWithinNolintBegin (Context, SuppressionErrors, SM, Loc, CheckName,
486- TextBeforeDiag, TextAfterDiag);
487- }
488-
489- static bool lineIsMarkedWithNOLINTinMacro (
490- const Diagnostic &Info, const ClangTidyContext &Context,
491- SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO,
492- bool EnableNolintBlocks) {
493- const SourceManager &SM = Info.getSourceManager ();
494- SourceLocation Loc = Info.getLocation ();
495- std::string CheckName = Context.getCheckName (Info.getID ());
496- while (true ) {
497- if (lineIsMarkedWithNOLINT (Context, SuppressionErrors, AllowIO, SM, Loc,
498- CheckName, EnableNolintBlocks))
499- return true ;
500- if (!Loc.isMacroID ())
501- return false ;
502- Loc = SM.getImmediateExpansionRange (Loc).getBegin ();
503- }
504- return false ;
505- }
506-
507320namespace clang {
508321namespace tidy {
509322
510- bool shouldSuppressDiagnostic (
511- DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
512- ClangTidyContext &Context,
513- SmallVectorImpl<ClangTidyError> &SuppressionErrors, bool AllowIO,
514- bool EnableNolintBlocks) {
515- return Info.getLocation ().isValid () &&
516- DiagLevel != DiagnosticsEngine::Error &&
517- DiagLevel != DiagnosticsEngine::Fatal &&
518- lineIsMarkedWithNOLINTinMacro (Info, Context, SuppressionErrors,
519- AllowIO, EnableNolintBlocks);
520- }
521-
522323const llvm::StringMap<tooling::Replacements> *
523324getFixIt (const tooling::Diagnostic &Diagnostic, bool GetFixFromNotes) {
524325 if (!Diagnostic.Message .Fix .empty ())
@@ -545,12 +346,15 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
545346 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
546347 return ;
547348
548- SmallVector<ClangTidyError , 1 > SuppressionErrors;
549- if (shouldSuppressDiagnostic (DiagLevel, Info, Context , SuppressionErrors,
550- EnableNolintBlocks)) {
349+ SmallVector<tooling::Diagnostic , 1 > SuppressionErrors;
350+ if (Context. shouldSuppressDiagnostic (DiagLevel, Info, SuppressionErrors,
351+ EnableNolintBlocks)) {
551352 ++Context.Stats .ErrorsIgnoredNOLINT ;
552353 // Ignored a warning, should ignore related notes as well
553354 LastErrorWasIgnored = true ;
355+ Context.DiagEngine ->Clear ();
356+ for (const auto &Error : SuppressionErrors)
357+ Context.diag (Error);
554358 return ;
555359 }
556360
0 commit comments