xref: /llvm-project/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
15da7c040SSalman Javed //===-- clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp -----------===//
25da7c040SSalman Javed //
35da7c040SSalman Javed // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45da7c040SSalman Javed // See https://llvm.org/LICENSE.txt for license information.
55da7c040SSalman Javed // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65da7c040SSalman Javed //
75da7c040SSalman Javed //===----------------------------------------------------------------------===//
85da7c040SSalman Javed ///
95da7c040SSalman Javed ///  \file This file implements the NoLintDirectiveHandler class, which is used
105da7c040SSalman Javed ///  to locate NOLINT comments in the file being analyzed, to decide whether a
115da7c040SSalman Javed ///  diagnostic should be suppressed.
125da7c040SSalman Javed ///
135da7c040SSalman Javed //===----------------------------------------------------------------------===//
145da7c040SSalman Javed 
155da7c040SSalman Javed #include "NoLintDirectiveHandler.h"
165da7c040SSalman Javed #include "GlobList.h"
175da7c040SSalman Javed #include "clang/Basic/LLVM.h"
185da7c040SSalman Javed #include "clang/Basic/SourceLocation.h"
195da7c040SSalman Javed #include "clang/Basic/SourceManager.h"
205da7c040SSalman Javed #include "clang/Tooling/Core/Diagnostic.h"
215da7c040SSalman Javed #include "llvm/ADT/ArrayRef.h"
225da7c040SSalman Javed #include "llvm/ADT/STLExtras.h"
235da7c040SSalman Javed #include "llvm/ADT/SmallVector.h"
245da7c040SSalman Javed #include "llvm/ADT/StringExtras.h"
255da7c040SSalman Javed #include "llvm/ADT/StringMap.h"
265da7c040SSalman Javed #include "llvm/ADT/StringSwitch.h"
275da7c040SSalman Javed #include <cassert>
285da7c040SSalman Javed #include <cstddef>
295da7c040SSalman Javed #include <iterator>
3071f55735SKazu Hirata #include <optional>
315da7c040SSalman Javed #include <string>
325da7c040SSalman Javed #include <tuple>
335da7c040SSalman Javed #include <type_traits>
345da7c040SSalman Javed #include <utility>
355da7c040SSalman Javed 
36*7d2ea6c4SCarlos Galvez namespace clang::tidy {
375da7c040SSalman Javed 
385da7c040SSalman Javed //===----------------------------------------------------------------------===//
395da7c040SSalman Javed // NoLintType
405da7c040SSalman Javed //===----------------------------------------------------------------------===//
415da7c040SSalman Javed 
425da7c040SSalman Javed // The type - one of NOLINT[NEXTLINE/BEGIN/END].
435da7c040SSalman Javed enum class NoLintType { NoLint, NoLintNextLine, NoLintBegin, NoLintEnd };
445da7c040SSalman Javed 
455da7c040SSalman Javed // Convert a string like "NOLINTNEXTLINE" to its enum `Type::NoLintNextLine`.
46aee59100SKazu Hirata // Return `std::nullopt` if the string is unrecognized.
strToNoLintType(StringRef Str)47f71ffd3bSKazu Hirata static std::optional<NoLintType> strToNoLintType(StringRef Str) {
48f71ffd3bSKazu Hirata   auto Type = llvm::StringSwitch<std::optional<NoLintType>>(Str)
495da7c040SSalman Javed                   .Case("NOLINT", NoLintType::NoLint)
505da7c040SSalman Javed                   .Case("NOLINTNEXTLINE", NoLintType::NoLintNextLine)
515da7c040SSalman Javed                   .Case("NOLINTBEGIN", NoLintType::NoLintBegin)
525da7c040SSalman Javed                   .Case("NOLINTEND", NoLintType::NoLintEnd)
53cd8702efSKazu Hirata                   .Default(std::nullopt);
545da7c040SSalman Javed   return Type;
555da7c040SSalman Javed }
565da7c040SSalman Javed 
575da7c040SSalman Javed //===----------------------------------------------------------------------===//
585da7c040SSalman Javed // NoLintToken
595da7c040SSalman Javed //===----------------------------------------------------------------------===//
605da7c040SSalman Javed 
615da7c040SSalman Javed // Whitespace within a NOLINT's check list shall be ignored.
625da7c040SSalman Javed // "NOLINT( check1, check2 )" is equivalent to "NOLINT(check1,check2)".
635da7c040SSalman Javed // Return the check list with all extraneous whitespace removed.
trimWhitespace(StringRef Checks)645da7c040SSalman Javed static std::string trimWhitespace(StringRef Checks) {
655da7c040SSalman Javed   SmallVector<StringRef> Split;
665da7c040SSalman Javed   Checks.split(Split, ',');
675da7c040SSalman Javed   for (StringRef &Check : Split)
685da7c040SSalman Javed     Check = Check.trim();
695da7c040SSalman Javed   return llvm::join(Split, ",");
705da7c040SSalman Javed }
715da7c040SSalman Javed 
725da7c040SSalman Javed namespace {
735da7c040SSalman Javed 
745da7c040SSalman Javed // Record the presence of a NOLINT comment - its type, location, checks -
755da7c040SSalman Javed // as parsed from the file's character contents.
765da7c040SSalman Javed class NoLintToken {
775da7c040SSalman Javed public:
785da7c040SSalman Javed   // \param Checks:
795da7c040SSalman Javed   // - If unspecified (i.e. `None`) then ALL checks are suppressed - equivalent
805da7c040SSalman Javed   //   to NOLINT(*).
815da7c040SSalman Javed   // - An empty string means nothing is suppressed - equivalent to NOLINT().
825da7c040SSalman Javed   // - Negative globs ignored (which would effectively disable the suppression).
NoLintToken(NoLintType Type,size_t Pos,const std::optional<std::string> & Checks)83f71ffd3bSKazu Hirata   NoLintToken(NoLintType Type, size_t Pos,
84f71ffd3bSKazu Hirata               const std::optional<std::string> &Checks)
855da7c040SSalman Javed       : Type(Type), Pos(Pos), ChecksGlob(std::make_unique<CachedGlobList>(
865dd171dcSKazu Hirata                                   Checks.value_or("*"),
875da7c040SSalman Javed                                   /*KeepNegativeGlobs=*/false)) {
885da7c040SSalman Javed     if (Checks)
895da7c040SSalman Javed       this->Checks = trimWhitespace(*Checks);
905da7c040SSalman Javed   }
915da7c040SSalman Javed 
925da7c040SSalman Javed   // The type - one of NOLINT[NEXTLINE/BEGIN/END].
935da7c040SSalman Javed   NoLintType Type;
945da7c040SSalman Javed 
955da7c040SSalman Javed   // The location of the first character, "N", in "NOLINT".
965da7c040SSalman Javed   size_t Pos;
975da7c040SSalman Javed 
985da7c040SSalman Javed   // If this NOLINT specifies checks, return the checks.
checks() const99f71ffd3bSKazu Hirata   std::optional<std::string> checks() const { return Checks; }
1005da7c040SSalman Javed 
1015da7c040SSalman Javed   // Whether this NOLINT applies to the provided check.
suppresses(StringRef Check) const1025da7c040SSalman Javed   bool suppresses(StringRef Check) const { return ChecksGlob->contains(Check); }
1035da7c040SSalman Javed 
1045da7c040SSalman Javed private:
105f71ffd3bSKazu Hirata   std::optional<std::string> Checks;
1065da7c040SSalman Javed   std::unique_ptr<CachedGlobList> ChecksGlob;
1075da7c040SSalman Javed };
1085da7c040SSalman Javed 
1095da7c040SSalman Javed } // namespace
1105da7c040SSalman Javed 
1115da7c040SSalman Javed // Consume the entire buffer and return all `NoLintToken`s that were found.
getNoLints(StringRef Buffer)1125da7c040SSalman Javed static SmallVector<NoLintToken> getNoLints(StringRef Buffer) {
1135da7c040SSalman Javed   static constexpr llvm::StringLiteral NOLINT = "NOLINT";
1145da7c040SSalman Javed   SmallVector<NoLintToken> NoLints;
1155da7c040SSalman Javed 
1165da7c040SSalman Javed   size_t Pos = 0;
1175da7c040SSalman Javed   while (Pos < Buffer.size()) {
1185da7c040SSalman Javed     // Find NOLINT:
1195da7c040SSalman Javed     const size_t NoLintPos = Buffer.find(NOLINT, Pos);
1205da7c040SSalman Javed     if (NoLintPos == StringRef::npos)
1215da7c040SSalman Javed       break; // Buffer exhausted
1225da7c040SSalman Javed 
1235da7c040SSalman Javed     // Read [A-Z] characters immediately after "NOLINT", e.g. the "NEXTLINE" in
1245da7c040SSalman Javed     // "NOLINTNEXTLINE".
1255da7c040SSalman Javed     Pos = NoLintPos + NOLINT.size();
1265da7c040SSalman Javed     while (Pos < Buffer.size() && llvm::isAlpha(Buffer[Pos]))
1275da7c040SSalman Javed       ++Pos;
1285da7c040SSalman Javed 
1295da7c040SSalman Javed     // Is this a recognized NOLINT type?
130f71ffd3bSKazu Hirata     const std::optional<NoLintType> NoLintType =
1315da7c040SSalman Javed         strToNoLintType(Buffer.slice(NoLintPos, Pos));
1325da7c040SSalman Javed     if (!NoLintType)
1335da7c040SSalman Javed       continue;
1345da7c040SSalman Javed 
1355da7c040SSalman Javed     // Get checks, if specified.
136f71ffd3bSKazu Hirata     std::optional<std::string> Checks;
1375da7c040SSalman Javed     if (Pos < Buffer.size() && Buffer[Pos] == '(') {
1385da7c040SSalman Javed       size_t ClosingBracket = Buffer.find_first_of("\n)", ++Pos);
1395da7c040SSalman Javed       if (ClosingBracket != StringRef::npos && Buffer[ClosingBracket] == ')') {
1405da7c040SSalman Javed         Checks = Buffer.slice(Pos, ClosingBracket).str();
1415da7c040SSalman Javed         Pos = ClosingBracket + 1;
1425da7c040SSalman Javed       }
1435da7c040SSalman Javed     }
1445da7c040SSalman Javed 
1455da7c040SSalman Javed     NoLints.emplace_back(*NoLintType, NoLintPos, Checks);
1465da7c040SSalman Javed   }
1475da7c040SSalman Javed 
1485da7c040SSalman Javed   return NoLints;
1495da7c040SSalman Javed }
1505da7c040SSalman Javed 
1515da7c040SSalman Javed //===----------------------------------------------------------------------===//
1525da7c040SSalman Javed // NoLintBlockToken
1535da7c040SSalman Javed //===----------------------------------------------------------------------===//
1545da7c040SSalman Javed 
1555da7c040SSalman Javed namespace {
1565da7c040SSalman Javed 
1575da7c040SSalman Javed // Represents a source range within a pair of NOLINT(BEGIN/END) comments.
1585da7c040SSalman Javed class NoLintBlockToken {
1595da7c040SSalman Javed public:
NoLintBlockToken(NoLintToken Begin,const NoLintToken & End)1605da7c040SSalman Javed   NoLintBlockToken(NoLintToken Begin, const NoLintToken &End)
1615da7c040SSalman Javed       : Begin(std::move(Begin)), EndPos(End.Pos) {
1625da7c040SSalman Javed     assert(this->Begin.Type == NoLintType::NoLintBegin);
1635da7c040SSalman Javed     assert(End.Type == NoLintType::NoLintEnd);
1645da7c040SSalman Javed     assert(this->Begin.Pos < End.Pos);
1655da7c040SSalman Javed     assert(this->Begin.checks() == End.checks());
1665da7c040SSalman Javed   }
1675da7c040SSalman Javed 
1685da7c040SSalman Javed   // Whether the provided diagnostic is within and is suppressible by this block
1695da7c040SSalman Javed   // of NOLINT(BEGIN/END) comments.
suppresses(size_t DiagPos,StringRef DiagName) const1705da7c040SSalman Javed   bool suppresses(size_t DiagPos, StringRef DiagName) const {
1715da7c040SSalman Javed     return (Begin.Pos < DiagPos) && (DiagPos < EndPos) &&
1725da7c040SSalman Javed            Begin.suppresses(DiagName);
1735da7c040SSalman Javed   }
1745da7c040SSalman Javed 
1755da7c040SSalman Javed private:
1765da7c040SSalman Javed   NoLintToken Begin;
1775da7c040SSalman Javed   size_t EndPos;
1785da7c040SSalman Javed };
1795da7c040SSalman Javed 
1805da7c040SSalman Javed } // namespace
1815da7c040SSalman Javed 
1825da7c040SSalman Javed // Match NOLINTBEGINs with their corresponding NOLINTENDs and move them into
1835da7c040SSalman Javed // `NoLintBlockToken`s. If any BEGINs or ENDs are left over, they are moved to
1845da7c040SSalman Javed // `UnmatchedTokens`.
1855da7c040SSalman Javed static SmallVector<NoLintBlockToken>
formNoLintBlocks(SmallVector<NoLintToken> NoLints,SmallVectorImpl<NoLintToken> & UnmatchedTokens)1865da7c040SSalman Javed formNoLintBlocks(SmallVector<NoLintToken> NoLints,
1875da7c040SSalman Javed                  SmallVectorImpl<NoLintToken> &UnmatchedTokens) {
1885da7c040SSalman Javed   SmallVector<NoLintBlockToken> CompletedBlocks;
1895da7c040SSalman Javed   SmallVector<NoLintToken> Stack;
1905da7c040SSalman Javed 
1915da7c040SSalman Javed   // Nested blocks must be fully contained within their parent block. What this
1925da7c040SSalman Javed   // means is that when you have a series of nested BEGIN tokens, the END tokens
1935da7c040SSalman Javed   // shall appear in the reverse order, starting with the closing of the
1945da7c040SSalman Javed   // inner-most block first, then the next level up, and so on. This is
1955da7c040SSalman Javed   // essentially a last-in-first-out/stack system.
1965da7c040SSalman Javed   for (NoLintToken &NoLint : NoLints) {
1975da7c040SSalman Javed     if (NoLint.Type == NoLintType::NoLintBegin)
1985da7c040SSalman Javed       // A new block is being started. Add it to the stack.
1995da7c040SSalman Javed       Stack.emplace_back(std::move(NoLint));
2005da7c040SSalman Javed     else if (NoLint.Type == NoLintType::NoLintEnd) {
2015da7c040SSalman Javed       if (!Stack.empty() && Stack.back().checks() == NoLint.checks())
2025da7c040SSalman Javed         // The previous block is being closed. Pop one element off the stack.
2035da7c040SSalman Javed         CompletedBlocks.emplace_back(Stack.pop_back_val(), NoLint);
2045da7c040SSalman Javed       else
2055da7c040SSalman Javed         // Trying to close the wrong block.
2065da7c040SSalman Javed         UnmatchedTokens.emplace_back(std::move(NoLint));
2075da7c040SSalman Javed     }
2085da7c040SSalman Javed   }
2095da7c040SSalman Javed 
2105da7c040SSalman Javed   llvm::move(Stack, std::back_inserter(UnmatchedTokens));
2115da7c040SSalman Javed   return CompletedBlocks;
2125da7c040SSalman Javed }
2135da7c040SSalman Javed 
2145da7c040SSalman Javed //===----------------------------------------------------------------------===//
2155da7c040SSalman Javed // NoLintDirectiveHandler::Impl
2165da7c040SSalman Javed //===----------------------------------------------------------------------===//
2175da7c040SSalman Javed 
2185da7c040SSalman Javed class NoLintDirectiveHandler::Impl {
2195da7c040SSalman Javed public:
2205da7c040SSalman Javed   bool shouldSuppress(DiagnosticsEngine::Level DiagLevel,
2215da7c040SSalman Javed                       const Diagnostic &Diag, StringRef DiagName,
2225da7c040SSalman Javed                       SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
2235da7c040SSalman Javed                       bool AllowIO, bool EnableNoLintBlocks);
2245da7c040SSalman Javed 
2255da7c040SSalman Javed private:
2265da7c040SSalman Javed   bool diagHasNoLintInMacro(const Diagnostic &Diag, StringRef DiagName,
2275da7c040SSalman Javed                             SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
2285da7c040SSalman Javed                             bool AllowIO, bool EnableNoLintBlocks);
2295da7c040SSalman Javed 
2305da7c040SSalman Javed   bool diagHasNoLint(StringRef DiagName, SourceLocation DiagLoc,
2315da7c040SSalman Javed                      const SourceManager &SrcMgr,
2325da7c040SSalman Javed                      SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
2335da7c040SSalman Javed                      bool AllowIO, bool EnableNoLintBlocks);
2345da7c040SSalman Javed 
2355da7c040SSalman Javed   void generateCache(const SourceManager &SrcMgr, StringRef FileName,
2365da7c040SSalman Javed                      FileID File, StringRef Buffer,
2375da7c040SSalman Javed                      SmallVectorImpl<tooling::Diagnostic> &NoLintErrors);
2385da7c040SSalman Javed 
2395da7c040SSalman Javed   llvm::StringMap<SmallVector<NoLintBlockToken>> Cache;
2405da7c040SSalman Javed };
2415da7c040SSalman Javed 
shouldSuppress(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Diag,StringRef DiagName,SmallVectorImpl<tooling::Diagnostic> & NoLintErrors,bool AllowIO,bool EnableNoLintBlocks)2425da7c040SSalman Javed bool NoLintDirectiveHandler::Impl::shouldSuppress(
2435da7c040SSalman Javed     DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag,
2445da7c040SSalman Javed     StringRef DiagName, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
2455da7c040SSalman Javed     bool AllowIO, bool EnableNoLintBlocks) {
2465da7c040SSalman Javed   if (DiagLevel >= DiagnosticsEngine::Error)
2475da7c040SSalman Javed     return false;
2485da7c040SSalman Javed   return diagHasNoLintInMacro(Diag, DiagName, NoLintErrors, AllowIO,
2495da7c040SSalman Javed                               EnableNoLintBlocks);
2505da7c040SSalman Javed }
2515da7c040SSalman Javed 
2525da7c040SSalman Javed // Look at the macro's spelling location for a NOLINT. If none is found, keep
2535da7c040SSalman Javed // looking up the call stack.
diagHasNoLintInMacro(const Diagnostic & Diag,StringRef DiagName,SmallVectorImpl<tooling::Diagnostic> & NoLintErrors,bool AllowIO,bool EnableNoLintBlocks)2545da7c040SSalman Javed bool NoLintDirectiveHandler::Impl::diagHasNoLintInMacro(
2555da7c040SSalman Javed     const Diagnostic &Diag, StringRef DiagName,
2565da7c040SSalman Javed     SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
2575da7c040SSalman Javed     bool EnableNoLintBlocks) {
2585da7c040SSalman Javed   SourceLocation DiagLoc = Diag.getLocation();
2595da7c040SSalman Javed   if (DiagLoc.isInvalid())
2605da7c040SSalman Javed     return false;
2615da7c040SSalman Javed   const SourceManager &SrcMgr = Diag.getSourceManager();
2625da7c040SSalman Javed   while (true) {
2635da7c040SSalman Javed     if (diagHasNoLint(DiagName, DiagLoc, SrcMgr, NoLintErrors, AllowIO,
2645da7c040SSalman Javed                       EnableNoLintBlocks))
2655da7c040SSalman Javed       return true;
2665da7c040SSalman Javed     if (!DiagLoc.isMacroID())
2675da7c040SSalman Javed       return false;
2689ff4f2dfSSalman Javed     DiagLoc = SrcMgr.getImmediateExpansionRange(DiagLoc).getBegin();
2695da7c040SSalman Javed   }
2705da7c040SSalman Javed   return false;
2715da7c040SSalman Javed }
2725da7c040SSalman Javed 
2735da7c040SSalman Javed // Look behind and ahead for '\n' characters. These mark the start and end of
2745da7c040SSalman Javed // this line.
getLineStartAndEnd(StringRef Buffer,size_t From)2755da7c040SSalman Javed static std::pair<size_t, size_t> getLineStartAndEnd(StringRef Buffer,
2765da7c040SSalman Javed                                                     size_t From) {
2775da7c040SSalman Javed   size_t StartPos = Buffer.find_last_of('\n', From) + 1;
2785da7c040SSalman Javed   size_t EndPos = std::min(Buffer.find('\n', From), Buffer.size());
2795da7c040SSalman Javed   return std::make_pair(StartPos, EndPos);
2805da7c040SSalman Javed }
2815da7c040SSalman Javed 
2825da7c040SSalman Javed // Whether the line has a NOLINT of type = `Type` that can suppress the
2835da7c040SSalman Javed // diagnostic `DiagName`.
lineHasNoLint(StringRef Buffer,std::pair<size_t,size_t> LineStartAndEnd,NoLintType Type,StringRef DiagName)2845da7c040SSalman Javed static bool lineHasNoLint(StringRef Buffer,
2855da7c040SSalman Javed                           std::pair<size_t, size_t> LineStartAndEnd,
2865da7c040SSalman Javed                           NoLintType Type, StringRef DiagName) {
2875da7c040SSalman Javed   // Get all NOLINTs on the line.
2885da7c040SSalman Javed   Buffer = Buffer.slice(LineStartAndEnd.first, LineStartAndEnd.second);
2895da7c040SSalman Javed   SmallVector<NoLintToken> NoLints = getNoLints(Buffer);
2905da7c040SSalman Javed 
2915da7c040SSalman Javed   // Do any of these NOLINTs match the desired type and diag name?
2925da7c040SSalman Javed   return llvm::any_of(NoLints, [&](const NoLintToken &NoLint) {
2935da7c040SSalman Javed     return NoLint.Type == Type && NoLint.suppresses(DiagName);
2945da7c040SSalman Javed   });
2955da7c040SSalman Javed }
2965da7c040SSalman Javed 
2975da7c040SSalman Javed // Whether the provided diagnostic is located within and is suppressible by a
2985da7c040SSalman Javed // block of NOLINT(BEGIN/END) comments.
withinNoLintBlock(ArrayRef<NoLintBlockToken> NoLintBlocks,size_t DiagPos,StringRef DiagName)2995da7c040SSalman Javed static bool withinNoLintBlock(ArrayRef<NoLintBlockToken> NoLintBlocks,
3005da7c040SSalman Javed                               size_t DiagPos, StringRef DiagName) {
3015da7c040SSalman Javed   return llvm::any_of(NoLintBlocks, [&](const NoLintBlockToken &NoLintBlock) {
3025da7c040SSalman Javed     return NoLintBlock.suppresses(DiagPos, DiagName);
3035da7c040SSalman Javed   });
3045da7c040SSalman Javed }
3055da7c040SSalman Javed 
3065da7c040SSalman Javed // Get the file contents as a string.
getBuffer(const SourceManager & SrcMgr,FileID File,bool AllowIO)307f71ffd3bSKazu Hirata static std::optional<StringRef> getBuffer(const SourceManager &SrcMgr,
308f71ffd3bSKazu Hirata                                           FileID File, bool AllowIO) {
3095da7c040SSalman Javed   return AllowIO ? SrcMgr.getBufferDataOrNone(File)
3105da7c040SSalman Javed                  : SrcMgr.getBufferDataIfLoaded(File);
3115da7c040SSalman Javed }
3125da7c040SSalman Javed 
3135da7c040SSalman Javed // We will check for NOLINTs and NOLINTNEXTLINEs first. Checking for these is
3145da7c040SSalman Javed // not so expensive (just need to parse the current and previous lines). Only if
3155da7c040SSalman Javed // that fails do we look for NOLINT(BEGIN/END) blocks (which requires reading
3165da7c040SSalman Javed // the entire file).
diagHasNoLint(StringRef DiagName,SourceLocation DiagLoc,const SourceManager & SrcMgr,SmallVectorImpl<tooling::Diagnostic> & NoLintErrors,bool AllowIO,bool EnableNoLintBlocks)3175da7c040SSalman Javed bool NoLintDirectiveHandler::Impl::diagHasNoLint(
3185da7c040SSalman Javed     StringRef DiagName, SourceLocation DiagLoc, const SourceManager &SrcMgr,
3195da7c040SSalman Javed     SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
3205da7c040SSalman Javed     bool EnableNoLintBlocks) {
3215da7c040SSalman Javed   // Translate the diagnostic's SourceLocation to a raw file + offset pair.
3225da7c040SSalman Javed   FileID File;
3235da7c040SSalman Javed   unsigned int Pos = 0;
3245da7c040SSalman Javed   std::tie(File, Pos) = SrcMgr.getDecomposedSpellingLoc(DiagLoc);
3255da7c040SSalman Javed 
3265da7c040SSalman Javed   // We will only see NOLINTs in user-authored sources. No point reading the
3275da7c040SSalman Javed   // file if it is a <built-in>.
328f71ffd3bSKazu Hirata   std::optional<StringRef> FileName = SrcMgr.getNonBuiltinFilenameForID(File);
3295da7c040SSalman Javed   if (!FileName)
3305da7c040SSalman Javed     return false;
3315da7c040SSalman Javed 
3325da7c040SSalman Javed   // Get file contents.
333f71ffd3bSKazu Hirata   std::optional<StringRef> Buffer = getBuffer(SrcMgr, File, AllowIO);
3345da7c040SSalman Javed   if (!Buffer)
3355da7c040SSalman Javed     return false;
3365da7c040SSalman Javed 
3375da7c040SSalman Javed   // Check if there's a NOLINT on this line.
3385da7c040SSalman Javed   auto ThisLine = getLineStartAndEnd(*Buffer, Pos);
3395da7c040SSalman Javed   if (lineHasNoLint(*Buffer, ThisLine, NoLintType::NoLint, DiagName))
3405da7c040SSalman Javed     return true;
3415da7c040SSalman Javed 
3425da7c040SSalman Javed   // Check if there's a NOLINTNEXTLINE on the previous line.
3435da7c040SSalman Javed   if (ThisLine.first > 0) {
3445da7c040SSalman Javed     auto PrevLine = getLineStartAndEnd(*Buffer, ThisLine.first - 1);
3455da7c040SSalman Javed     if (lineHasNoLint(*Buffer, PrevLine, NoLintType::NoLintNextLine, DiagName))
3465da7c040SSalman Javed       return true;
3475da7c040SSalman Javed   }
3485da7c040SSalman Javed 
3495da7c040SSalman Javed   // Check if this line is within a NOLINT(BEGIN/END) block.
3505da7c040SSalman Javed   if (!EnableNoLintBlocks)
3515da7c040SSalman Javed     return false;
3525da7c040SSalman Javed 
3535da7c040SSalman Javed   // Do we have cached NOLINT block locations for this file?
3545da7c040SSalman Javed   if (Cache.count(*FileName) == 0)
3555da7c040SSalman Javed     // Warning: heavy operation - need to read entire file.
3565da7c040SSalman Javed     generateCache(SrcMgr, *FileName, File, *Buffer, NoLintErrors);
3575da7c040SSalman Javed 
3585da7c040SSalman Javed   return withinNoLintBlock(Cache[*FileName], Pos, DiagName);
3595da7c040SSalman Javed }
3605da7c040SSalman Javed 
3615da7c040SSalman Javed // Construct a [clang-tidy-nolint] diagnostic to do with the unmatched
3625da7c040SSalman Javed // NOLINT(BEGIN/END) pair.
makeNoLintError(const SourceManager & SrcMgr,FileID File,const NoLintToken & NoLint)3635da7c040SSalman Javed static tooling::Diagnostic makeNoLintError(const SourceManager &SrcMgr,
3645da7c040SSalman Javed                                            FileID File,
3655da7c040SSalman Javed                                            const NoLintToken &NoLint) {
3665da7c040SSalman Javed   tooling::Diagnostic Error;
3675da7c040SSalman Javed   Error.DiagLevel = tooling::Diagnostic::Error;
3685da7c040SSalman Javed   Error.DiagnosticName = "clang-tidy-nolint";
3695da7c040SSalman Javed   StringRef Message =
3705da7c040SSalman Javed       (NoLint.Type == NoLintType::NoLintBegin)
3715da7c040SSalman Javed           ? ("unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
3725da7c040SSalman Javed              "END' comment")
3735da7c040SSalman Javed           : ("unmatched 'NOLINTEND' comment without a previous 'NOLINT"
3745da7c040SSalman Javed              "BEGIN' comment");
3755da7c040SSalman Javed   SourceLocation Loc = SrcMgr.getComposedLoc(File, NoLint.Pos);
3765da7c040SSalman Javed   Error.Message = tooling::DiagnosticMessage(Message, SrcMgr, Loc);
3775da7c040SSalman Javed   return Error;
3785da7c040SSalman Javed }
3795da7c040SSalman Javed 
3805da7c040SSalman Javed // Find all NOLINT(BEGIN/END) blocks in a file and store in the cache.
generateCache(const SourceManager & SrcMgr,StringRef FileName,FileID File,StringRef Buffer,SmallVectorImpl<tooling::Diagnostic> & NoLintErrors)3815da7c040SSalman Javed void NoLintDirectiveHandler::Impl::generateCache(
3825da7c040SSalman Javed     const SourceManager &SrcMgr, StringRef FileName, FileID File,
3835da7c040SSalman Javed     StringRef Buffer, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors) {
3845da7c040SSalman Javed   // Read entire file to get all NOLINTs.
3855da7c040SSalman Javed   SmallVector<NoLintToken> NoLints = getNoLints(Buffer);
3865da7c040SSalman Javed 
3875da7c040SSalman Javed   // Match each BEGIN with its corresponding END.
3885da7c040SSalman Javed   SmallVector<NoLintToken> UnmatchedTokens;
3895da7c040SSalman Javed   Cache[FileName] = formNoLintBlocks(std::move(NoLints), UnmatchedTokens);
3905da7c040SSalman Javed 
3915da7c040SSalman Javed   // Raise error for any BEGIN/END left over.
3925da7c040SSalman Javed   for (const NoLintToken &NoLint : UnmatchedTokens)
3935da7c040SSalman Javed     NoLintErrors.emplace_back(makeNoLintError(SrcMgr, File, NoLint));
3945da7c040SSalman Javed }
3955da7c040SSalman Javed 
3965da7c040SSalman Javed //===----------------------------------------------------------------------===//
3975da7c040SSalman Javed // NoLintDirectiveHandler
3985da7c040SSalman Javed //===----------------------------------------------------------------------===//
3995da7c040SSalman Javed 
NoLintDirectiveHandler()4005da7c040SSalman Javed NoLintDirectiveHandler::NoLintDirectiveHandler()
4015da7c040SSalman Javed     : PImpl(std::make_unique<Impl>()) {}
4025da7c040SSalman Javed 
4035da7c040SSalman Javed NoLintDirectiveHandler::~NoLintDirectiveHandler() = default;
4045da7c040SSalman Javed 
shouldSuppress(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Diag,StringRef DiagName,SmallVectorImpl<tooling::Diagnostic> & NoLintErrors,bool AllowIO,bool EnableNoLintBlocks)4055da7c040SSalman Javed bool NoLintDirectiveHandler::shouldSuppress(
4065da7c040SSalman Javed     DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag,
4075da7c040SSalman Javed     StringRef DiagName, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
4085da7c040SSalman Javed     bool AllowIO, bool EnableNoLintBlocks) {
4095da7c040SSalman Javed   return PImpl->shouldSuppress(DiagLevel, Diag, DiagName, NoLintErrors, AllowIO,
4105da7c040SSalman Javed                                EnableNoLintBlocks);
4115da7c040SSalman Javed }
4125da7c040SSalman Javed 
413*7d2ea6c4SCarlos Galvez } // namespace clang::tidy
414