17#include "clang/Basic/LLVM.h"
18#include "clang/Basic/SourceLocation.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Tooling/Core/Diagnostic.h"
21#include "llvm/ADT/STLExtras.h"
22#include "llvm/ADT/StringExtras.h"
23#include "llvm/ADT/StringMap.h"
24#include "llvm/ADT/StringSwitch.h"
41enum class NoLintType { NoLint, NoLintNextLine, NoLintBegin, NoLintEnd };
48 auto Type = llvm::StringSwitch<std::optional<NoLintType>>(Str)
49 .Case(
"NOLINT", NoLintType::NoLint)
50 .Case(
"NOLINTNEXTLINE", NoLintType::NoLintNextLine)
51 .Case(
"NOLINTBEGIN", NoLintType::NoLintBegin)
52 .Case(
"NOLINTEND", NoLintType::NoLintEnd)
53 .Default(std::nullopt);
65 SmallVector<StringRef> Split;
67 for (StringRef &Check : Split)
69 return llvm::join(Split,
",");
83 NoLintToken(NoLintType Type,
size_t Pos,
84 const std::optional<StringRef> &
Checks)
85 : Type(Type), Pos(Pos), ChecksGlob(std::make_unique<CachedGlobList>(
99 std::unique_ptr<CachedGlobList> ChecksGlob;
102 const std::optional<std::string> &checks()
const {
return Checks; }
105 bool suppresses(StringRef Check)
const {
return ChecksGlob->contains(Check); }
108 std::optional<std::string> Checks;
114static SmallVector<NoLintToken>
getNoLints(StringRef Buffer) {
115 static constexpr StringRef NOLINT =
"NOLINT";
116 SmallVector<NoLintToken> NoLints;
119 while (Pos < Buffer.size()) {
121 const size_t NoLintPos = Buffer.find(NOLINT, Pos);
122 if (NoLintPos == StringRef::npos)
127 Pos = NoLintPos + NOLINT.size();
128 while (Pos < Buffer.size() && llvm::isAlpha(Buffer[Pos]))
132 const std::optional<NoLintType> NoLintType =
138 std::optional<StringRef>
Checks;
139 if (Pos < Buffer.size() && Buffer[Pos] ==
'(') {
140 const size_t ClosingBracket = Buffer.find_first_of(
"\n)", ++Pos);
141 if (ClosingBracket != StringRef::npos && Buffer[ClosingBracket] ==
')') {
142 Checks = Buffer.slice(Pos, ClosingBracket);
143 Pos = ClosingBracket + 1;
147 NoLints.emplace_back(*NoLintType, NoLintPos,
Checks);
160class NoLintBlockToken {
162 NoLintBlockToken(
size_t BeginPos,
size_t EndPos,
163 std::unique_ptr<CachedGlobList> ChecksGlob)
164 : BeginPos(BeginPos), EndPos(EndPos), ChecksGlob(std::
move(ChecksGlob)) {}
168 bool suppresses(
size_t DiagPos, StringRef DiagName)
const {
169 return (BeginPos < DiagPos) && (DiagPos < EndPos) &&
170 ChecksGlob->contains(DiagName);
176 std::unique_ptr<CachedGlobList> ChecksGlob;
185 const NoLintToken &NoLint) {
186 tooling::Diagnostic Error;
187 Error.DiagLevel = tooling::Diagnostic::Error;
188 Error.DiagnosticName =
"clang-tidy-nolint";
189 const StringRef Message =
190 (NoLint.Type == NoLintType::NoLintBegin)
191 ? (
"unmatched 'NOLINTBEGIN' comment without a subsequent 'NOLINT"
193 : (
"unmatched 'NOLINTEND' comment without a previous 'NOLINT"
195 const SourceLocation Loc = SrcMgr.getComposedLoc(File, NoLint.Pos);
196 Error.Message = tooling::DiagnosticMessage(Message, SrcMgr, Loc);
203static SmallVector<NoLintBlockToken>
206 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors) {
207 SmallVector<NoLintBlockToken> CompletedBlocks;
208 SmallVector<NoLintToken> Stack;
215 for (NoLintToken &NoLint : NoLints) {
216 if (NoLint.Type == NoLintType::NoLintBegin)
218 Stack.emplace_back(std::move(NoLint));
219 else if (NoLint.Type == NoLintType::NoLintEnd) {
220 if (!Stack.empty() && Stack.back().checks() == NoLint.checks()) {
222 CompletedBlocks.emplace_back(Stack.back().Pos, NoLint.Pos,
223 std::move(Stack.back().ChecksGlob));
231 for (
const NoLintToken &NoLint : Stack)
234 return CompletedBlocks;
244 const Diagnostic &Diag, StringRef DiagName,
245 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
246 bool AllowIO,
bool EnableNoLintBlocks);
249 bool diagHasNoLintInMacro(
const Diagnostic &Diag, StringRef DiagName,
250 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
251 bool AllowIO,
bool EnableNoLintBlocks);
253 bool diagHasNoLint(StringRef DiagName, SourceLocation DiagLoc,
254 const SourceManager &SrcMgr,
255 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
256 bool AllowIO,
bool EnableNoLintBlocks);
258 void generateCache(
const SourceManager &SrcMgr, StringRef FileName,
259 FileID File, StringRef Buffer,
260 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors);
262 llvm::StringMap<SmallVector<NoLintBlockToken>> Cache;
266 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Diag,
267 StringRef DiagName, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
268 bool AllowIO,
bool EnableNoLintBlocks) {
269 if (DiagLevel >= DiagnosticsEngine::Error)
271 return diagHasNoLintInMacro(Diag, DiagName, NoLintErrors, AllowIO,
277bool NoLintDirectiveHandler::Impl::diagHasNoLintInMacro(
278 const Diagnostic &Diag, StringRef DiagName,
279 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
bool AllowIO,
280 bool EnableNoLintBlocks) {
281 SourceLocation DiagLoc = Diag.getLocation();
282 if (DiagLoc.isInvalid())
284 const SourceManager &SrcMgr = Diag.getSourceManager();
286 if (diagHasNoLint(DiagName, DiagLoc, SrcMgr, NoLintErrors, AllowIO,
289 if (!DiagLoc.isMacroID())
291 DiagLoc = SrcMgr.getImmediateExpansionRange(DiagLoc).getBegin();
300 const size_t StartPos = Buffer.find_last_of(
'\n', From) + 1;
301 const size_t EndPos = std::min(Buffer.find(
'\n', From), Buffer.size());
302 return {StartPos, EndPos};
308 std::pair<size_t, size_t> LineStartAndEnd,
309 NoLintType Type, StringRef DiagName) {
311 Buffer = Buffer.slice(LineStartAndEnd.first, LineStartAndEnd.second);
312 SmallVector<NoLintToken> NoLints =
getNoLints(Buffer);
315 return llvm::any_of(NoLints, [&](
const NoLintToken &NoLint) {
316 return NoLint.Type == Type && NoLint.suppresses(DiagName);
323 size_t DiagPos, StringRef DiagName) {
324 return llvm::any_of(NoLintBlocks, [&](
const NoLintBlockToken &NoLintBlock) {
325 return NoLintBlock.suppresses(DiagPos, DiagName);
330static std::optional<StringRef>
getBuffer(
const SourceManager &SrcMgr,
331 FileID File,
bool AllowIO) {
332 return AllowIO ? SrcMgr.getBufferDataOrNone(File)
333 : SrcMgr.getBufferDataIfLoaded(File);
340bool NoLintDirectiveHandler::Impl::diagHasNoLint(
341 StringRef DiagName, SourceLocation DiagLoc,
const SourceManager &SrcMgr,
342 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
bool AllowIO,
343 bool EnableNoLintBlocks) {
345 const auto [File, Pos] = SrcMgr.getDecomposedSpellingLoc(DiagLoc);
349 std::optional<StringRef> FileName = SrcMgr.getNonBuiltinFilenameForID(File);
354 std::optional<StringRef> Buffer =
getBuffer(SrcMgr, File, AllowIO);
360 if (
lineHasNoLint(*Buffer, ThisLine, NoLintType::NoLint, DiagName))
364 if (ThisLine.first > 0) {
366 if (
lineHasNoLint(*Buffer, PrevLine, NoLintType::NoLintNextLine, DiagName))
371 if (!EnableNoLintBlocks)
375 if (!Cache.contains(*FileName))
377 generateCache(SrcMgr, *FileName, File, *Buffer, NoLintErrors);
383void NoLintDirectiveHandler::Impl::generateCache(
384 const SourceManager &SrcMgr, StringRef FileName, FileID File,
385 StringRef Buffer, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors) {
397 : PImpl(std::make_unique<
Impl>()) {}
402 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Diag,
403 StringRef DiagName, SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
404 bool AllowIO,
bool EnableNoLintBlocks) {
405 return PImpl->shouldSuppress(DiagLevel, Diag, DiagName, NoLintErrors, AllowIO,
static cl::opt< std::string > Checks("checks", desc(R"(
Comma-separated list of globs with optional '-'
prefix. Globs are processed in order of
appearance in the list. Globs without '-'
prefix add checks with matching names to the
set, globs with the '-' prefix remove checks
with matching names from the set of enabled
checks. This option's value is appended to the
value of the 'Checks' option in .clang-tidy
file, if any.
)"), cl::init(""), cl::cat(ClangTidyCategory))
bool shouldSuppress(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag, StringRef DiagName, SmallVectorImpl< tooling::Diagnostic > &NoLintErrors, bool AllowIO, bool EnableNoLintBlocks)
~NoLintDirectiveHandler()
bool shouldSuppress(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Diag, llvm::StringRef DiagName, llvm::SmallVectorImpl< tooling::Diagnostic > &NoLintErrors, bool AllowIO, bool EnableNoLintBlocks)
static SmallVector< NoLintBlockToken > formNoLintBlocks(SmallVector< NoLintToken > NoLints, const SourceManager &SrcMgr, FileID File, SmallVectorImpl< tooling::Diagnostic > &NoLintErrors)
static std::optional< StringRef > getBuffer(const SourceManager &SrcMgr, FileID File, bool AllowIO)
static std::pair< size_t, size_t > getLineStartAndEnd(StringRef Buffer, size_t From)
static tooling::Diagnostic makeNoLintError(const SourceManager &SrcMgr, FileID File, const NoLintToken &NoLint)
static SmallVector< NoLintToken > getNoLints(StringRef Buffer)
static std::optional< NoLintType > strToNoLintType(StringRef Str)
static std::string trimWhitespace(StringRef Checks)
static bool withinNoLintBlock(ArrayRef< NoLintBlockToken > NoLintBlocks, size_t DiagPos, StringRef DiagName)
static bool lineHasNoLint(StringRef Buffer, std::pair< size_t, size_t > LineStartAndEnd, NoLintType Type, StringRef DiagName)