18
18
#include " ClangTidyDiagnosticConsumer.h"
19
19
#include " ClangTidyOptions.h"
20
20
#include " GlobList.h"
21
- #include " NoLintDirectiveHandler.h"
22
21
#include " clang/AST/ASTContext.h"
23
22
#include " clang/AST/ASTDiagnostic.h"
24
23
#include " clang/AST/Attr.h"
@@ -189,7 +188,7 @@ DiagnosticBuilder ClangTidyContext::diag(
189
188
return DiagEngine->Report (ID);
190
189
}
191
190
192
- DiagnosticBuilder ClangTidyContext::diag (const tooling::Diagnostic &Error) {
191
+ DiagnosticBuilder ClangTidyContext::diag (const ClangTidyError &Error) {
193
192
SourceManager &SM = DiagEngine->getSourceManager ();
194
193
llvm::ErrorOr<const FileEntry *> File =
195
194
SM.getFileManager ().getFile (Error.Message .FilePath );
@@ -207,15 +206,6 @@ DiagnosticBuilder ClangTidyContext::configurationDiag(
207
206
return diag (" clang-tidy-config" , Message, Level);
208
207
}
209
208
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
-
219
209
void ClangTidyContext::setSourceManager (SourceManager *SourceMgr) {
220
210
DiagEngine->setSourceManager (SourceMgr);
221
211
}
@@ -317,9 +307,218 @@ void ClangTidyDiagnosticConsumer::finalizeLastError() {
317
307
LastErrorPassesLineFilter = false ;
318
308
}
319
309
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
+
320
507
namespace clang {
321
508
namespace tidy {
322
509
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
+
323
522
const llvm::StringMap<tooling::Replacements> *
324
523
getFixIt (const tooling::Diagnostic &Diagnostic, bool GetFixFromNotes) {
325
524
if (!Diagnostic.Message .Fix .empty ())
@@ -346,15 +545,12 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
346
545
if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
347
546
return ;
348
547
349
- SmallVector<tooling::Diagnostic , 1 > SuppressionErrors;
350
- if (Context. shouldSuppressDiagnostic (DiagLevel, Info, SuppressionErrors,
351
- EnableNolintBlocks)) {
548
+ SmallVector<ClangTidyError , 1 > SuppressionErrors;
549
+ if (shouldSuppressDiagnostic (DiagLevel, Info, Context , SuppressionErrors,
550
+ EnableNolintBlocks)) {
352
551
++Context.Stats .ErrorsIgnoredNOLINT ;
353
552
// Ignored a warning, should ignore related notes as well
354
553
LastErrorWasIgnored = true ;
355
- Context.DiagEngine ->Clear ();
356
- for (const auto &Error : SuppressionErrors)
357
- Context.diag (Error);
358
554
return ;
359
555
}
360
556
0 commit comments