21 #include "clang/AST/ASTDiagnostic.h"
22 #include "clang/Basic/DiagnosticOptions.h"
23 #include "clang/Frontend/DiagnosticRenderer.h"
24 #include "llvm/ADT/SmallString.h"
27 using namespace clang;
31 class ClangTidyDiagnosticRenderer :
public DiagnosticRenderer {
33 ClangTidyDiagnosticRenderer(
const LangOptions &
LangOpts,
36 : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
39 void emitDiagnosticMessage(FullSourceLoc
Loc, PresumedLoc PLoc,
40 DiagnosticsEngine::Level Level, StringRef
Message,
41 ArrayRef<CharSourceRange> Ranges,
42 DiagOrStoredDiag Info)
override {
47 std::string CheckNameInMessage =
" [" + Error.DiagnosticName +
"]";
48 if (Message.endswith(CheckNameInMessage))
49 Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
53 ? tooling::DiagnosticMessage(Message, Loc.getManager(),
Loc)
54 : tooling::DiagnosticMessage(Message);
55 if (Level == DiagnosticsEngine::Note) {
56 Error.Notes.push_back(TidyMessage);
59 assert(Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
60 Error.Message = TidyMessage;
63 void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
64 DiagnosticsEngine::Level Level,
65 ArrayRef<CharSourceRange> Ranges)
override {}
67 void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
68 SmallVectorImpl<CharSourceRange> &Ranges,
69 ArrayRef<FixItHint> Hints)
override {
70 assert(Loc.isValid());
71 for (
const auto &FixIt : Hints) {
72 CharSourceRange
Range = FixIt.RemoveRange;
73 assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
74 "Invalid range in the fix-it hint.");
75 assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
76 "Only file locations supported in fix-it hints.");
78 tooling::Replacement Replacement(Loc.getManager(),
Range,
80 llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
84 llvm::errs() <<
"Fix conflicts with existing fix! "
86 assert(
false &&
"Fix conflicts with existing fix!");
91 void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc)
override {}
93 void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
94 StringRef ModuleName)
override {}
96 void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
97 StringRef ModuleName)
override {}
99 void endDiagnostic(DiagOrStoredDiag D,
100 DiagnosticsEngine::Level Level)
override {
101 assert(!Error.Message.Message.empty() &&
"Message has not been set");
110 ClangTidyError::Level DiagLevel,
111 StringRef BuildDirectory,
bool IsWarningAsError)
112 : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
113 IsWarningAsError(IsWarningAsError) {}
118 GlobList = GlobList.trim(
' ');
119 if (GlobList.startswith(
"-")) {
120 GlobList = GlobList.substr(1);
128 StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(
','));
129 StringRef Glob = UntrimmedGlob.trim(
' ');
130 GlobList = GlobList.substr(UntrimmedGlob.size() + 1);
131 SmallString<128> RegexText(
"^");
132 StringRef MetaChars(
"()^$|*+?.[]\\{}");
133 for (
char C : Glob) {
135 RegexText.push_back(
'.');
136 else if (MetaChars.find(C) != StringRef::npos)
137 RegexText.push_back(
'\\');
138 RegexText.push_back(C);
140 RegexText.push_back(
'$');
141 return llvm::Regex(RegexText);
146 NextGlob(Globs.empty() ? nullptr : new
GlobList(Globs)) {}
153 Contains = NextGlob->contains(S, Contains);
162 switch (
auto &Result = Cache[S]) {
163 case Yes:
return true;
164 case No:
return false;
166 Result = Globs.contains(S) ? Yes : No;
167 return Result == Yes;
169 llvm_unreachable(
"invalid enum");
174 enum Tristate { None, Yes, No };
175 llvm::StringMap<Tristate> Cache;
179 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
180 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
190 StringRef CheckName, SourceLocation Loc, StringRef Description,
191 DiagnosticIDs::Level Level ) {
192 assert(Loc.isValid());
193 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
194 Level, (Description +
" [" + CheckName +
"]").str());
195 CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
196 return DiagEngine->Report(Loc, ID);
199 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
204 DiagEngine->setSourceManager(SourceMgr);
211 WarningAsErrorFilter =
216 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
217 LangOpts = Context->getLangOpts();
221 return OptionsProvider->getGlobalOptions();
225 return CurrentOptions;
232 OptionsProvider->getOptions(File));
238 assert(CheckFilter !=
nullptr);
239 return CheckFilter->contains(CheckName);
243 assert(WarningAsErrorFilter !=
nullptr);
244 return WarningAsErrorFilter->contains(CheckName);
249 Errors.push_back(Error);
253 llvm::DenseMap<unsigned, std::string>::const_iterator I =
254 CheckNamesByDiagnosticID.find(DiagnosticID);
255 if (I != CheckNamesByDiagnosticID.end())
262 :
Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors),
263 LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
264 LastErrorWasIgnored(false) {
265 IntrusiveRefCntPtr<DiagnosticOptions>
DiagOpts =
new DiagnosticOptions();
266 Diags = llvm::make_unique<DiagnosticsEngine>(
267 IntrusiveRefCntPtr<DiagnosticIDs>(
new DiagnosticIDs), &*DiagOpts,
this,
269 Context.setDiagnosticsEngine(Diags.get());
272 void ClangTidyDiagnosticConsumer::finalizeLastError() {
273 if (!Errors.empty()) {
276 Error.DiagLevel != ClangTidyError::Error) {
279 }
else if (!LastErrorRelatesToUserCode) {
282 }
else if (!LastErrorPassesLineFilter) {
289 LastErrorRelatesToUserCode =
false;
290 LastErrorPassesLineFilter =
false;
295 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
300 const char *P = CharacterData;
301 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
303 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
305 if (RestOfLine.find(
"NOLINT") != StringRef::npos)
309 const char *BufBegin =
310 SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
311 if (Invalid || P == BufBegin)
316 while (P != BufBegin && *P !=
'\n')
325 const char *LineEnd = P;
328 while (P != BufBegin && *P !=
'\n')
331 RestOfLine = StringRef(P, LineEnd - P + 1);
332 if (RestOfLine.find(
"NOLINTNEXTLINE") != StringRef::npos)
339 SourceLocation Loc) {
343 if (!Loc.isMacroID())
345 Loc = SM.getImmediateExpansionRange(Loc).first;
351 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
352 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
355 if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
356 DiagLevel != DiagnosticsEngine::Fatal &&
358 Info.getLocation())) {
361 LastErrorWasIgnored =
true;
365 LastErrorWasIgnored =
false;
367 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
369 if (DiagLevel == DiagnosticsEngine::Note) {
370 assert(!Errors.empty() &&
371 "A diagnostic note can only be appended to a message.");
374 StringRef WarningOption =
375 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
377 std::string CheckName = !WarningOption.empty()
378 ? (
"clang-diagnostic-" + WarningOption).str()
381 if (CheckName.empty()) {
385 case DiagnosticsEngine::Error:
386 case DiagnosticsEngine::Fatal:
387 CheckName =
"clang-diagnostic-error";
389 case DiagnosticsEngine::Warning:
390 CheckName =
"clang-diagnostic-warning";
393 CheckName =
"clang-diagnostic-unknown";
398 ClangTidyError::Level Level = ClangTidyError::Warning;
399 if (DiagLevel == DiagnosticsEngine::Error ||
400 DiagLevel == DiagnosticsEngine::Fatal) {
403 Level = ClangTidyError::Error;
404 LastErrorRelatesToUserCode =
true;
405 LastErrorPassesLineFilter =
true;
407 bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
413 ClangTidyDiagnosticRenderer Converter(
414 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
417 Info.FormatDiagnostic(Message);
419 (Info.getLocation().isInvalid())
421 : FullSourceLoc(Info.getLocation(), Info.getSourceManager());
422 Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
423 Info.getFixItHints());
425 checkFilters(Info.getLocation());
428 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
429 unsigned LineNumber)
const {
433 if (FileName.endswith(Filter.Name)) {
434 if (Filter.LineRanges.empty())
437 if (Range.first <= LineNumber && LineNumber <= Range.second)
446 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location) {
448 if (!Location.isValid()) {
449 LastErrorRelatesToUserCode =
true;
450 LastErrorPassesLineFilter =
true;
454 const SourceManager &Sources = Diags->getSourceManager();
456 Sources.isInSystemHeader(Location))
462 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
463 const FileEntry *
File = Sources.getFileEntryForID(FID);
468 LastErrorRelatesToUserCode =
true;
469 LastErrorPassesLineFilter =
true;
473 StringRef FileName(File->getName());
474 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
475 Sources.isInMainFile(Location) ||
476 getHeaderFilter()->match(FileName);
478 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
479 LastErrorPassesLineFilter =
480 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
483 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
487 return HeaderFilter.get();
490 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
491 SmallVectorImpl<ClangTidyError> &Errors)
const {
507 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
509 : Type(Type), ErrorId(ErrorId) {
535 if (Type == ET_Begin)
536 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
538 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
541 bool operator<(
const Event &Other)
const {
542 return Priority < Other.Priority;
551 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
555 std::vector<int> Sizes;
556 for (
const auto &Error : Errors) {
558 for (
const auto &FileAndReplaces : Error.Fix) {
559 for (
const auto &Replace : FileAndReplaces.second)
560 Size += Replace.getLength();
562 Sizes.push_back(Size);
566 std::map<std::string, std::vector<Event>> FileEvents;
567 for (
unsigned I = 0; I < Errors.size(); ++I) {
568 for (
const auto &FileAndReplace : Errors[I].
Fix) {
569 for (
const auto &Replace : FileAndReplace.second) {
570 unsigned Begin = Replace.getOffset();
571 unsigned End = Begin + Replace.getLength();
572 const std::string &FilePath = Replace.getFilePath();
576 auto &Events = FileEvents[FilePath];
577 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
578 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
583 std::vector<bool> Apply(Errors.size(),
true);
584 for (
auto &FileAndEvents : FileEvents) {
585 std::vector<Event> &Events = FileAndEvents.second;
587 std::sort(Events.begin(), Events.end());
588 int OpenIntervals = 0;
589 for (
const auto &Event : Events) {
590 if (Event.Type == Event::ET_End)
594 if (OpenIntervals != 0)
595 Apply[Event.ErrorId] =
false;
596 if (Event.Type == Event::ET_Begin)
599 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
602 for (
unsigned I = 0; I < Errors.size(); ++I) {
604 Errors[I].Fix.clear();
605 Errors[I].Notes.emplace_back(
606 "this fix will not be applied because it overlaps with another fix");
612 struct LessClangTidyError {
614 const tooling::DiagnosticMessage &M1 = LHS.Message;
615 const tooling::DiagnosticMessage &M2 = RHS.Message;
617 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
618 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
621 struct EqualClangTidyError {
623 LessClangTidyError Less;
624 return !Less(LHS, RHS) && !Less(RHS, LHS);
633 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
634 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
637 if (RemoveIncompatibleErrors)
638 removeIncompatibleErrors(Errors);
641 Context.storeError(Error);
llvm::Optional< std::string > Checks
Checks filter.
SourceLocation Loc
'#' location in the include directive
Read-only set of strings represented as a list of positive and negative globs.
GlobList(StringRef Globs)
GlobList is a comma-separated list of globs (only '*' metacharacter is supported) with optional '-' p...
void finish() override
Flushes the internal diagnostics buffer to the ClangTidyContext.
bool contains(StringRef S)
Returns true if the pattern matches S.
llvm::Optional< std::string > HeaderFilterRegex
Output warnings from headers matching this filter.
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
static const StringRef Message
Contains options for clang-tidy.
unsigned ErrorsIgnoredCheckFilter
static bool ConsumeNegativeIndicator(StringRef &GlobList)
unsigned ErrorsIgnoredNOLINT
bool contains(StringRef S)
llvm::Optional< bool > SystemHeaders
Output warnings from system headers matching HeaderFilterRegex.
ClangTidyOptions getOptionsForFile(StringRef File) const
Returns options for File.
std::pair< unsigned, unsigned > LineRange
LineRange is a pair<start, end> (inclusive).
void setCurrentFile(StringRef File)
Should be called when starting to process new translation unit.
const ClangTidyOptions & getOptions() const
Returns options for CurrentFile.
static llvm::Regex ConsumeGlob(StringRef &GlobList)
DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, StringRef Message, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Report any errors detected using this method.
unsigned ErrorsIgnoredNonUserCode
void setCheckProfileData(ProfileData *Profile)
Set the output struct for profile data.
ClangTidyContext(std::unique_ptr< ClangTidyOptionsProvider > OptionsProvider)
Initializes ClangTidyContext instance.
ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, bool IsWarningAsError)
void setASTContext(ASTContext *Context)
Sets ASTContext for the current translation unit.
std::vector< FileFilter > LineFilter
Output warnings from certain line ranges of certain files only.
ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const
Creates a new ClangTidyOptions instance combined from all fields of this instance overridden by the f...
unsigned ErrorsIgnoredLineFilter
llvm::Optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc)
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
const LangOptions & getLangOpts() const
Gets the language options from the AST context.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
CachedGlobList(StringRef Globs)
Contains a list of line ranges in a single file.
CharSourceRange Range
SourceRange for the file name.
StringRef getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
A detected error complete with information to display diagnostic and automatic fix.
IntrusiveRefCntPtr< DiagnosticOptions > DiagOpts
ClangTidyContext & Context
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc)
bool treatAsError(StringRef CheckName) const
Returns true if the check should be upgraded to error for the CurrentFile.
static ClangTidyOptions getDefaults()
These options are used for all settings that haven't been overridden by the OptionsProvider.
static cl::opt< bool > Fix("fix", cl::desc(R"(
Apply suggested fixes. Without -fix-errors
clang-tidy will bail out if any compilation
errors were found.
)"), cl::init(false), cl::cat(ClangTidyCategory))
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, bool RemoveIncompatibleErrors=true)
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
Container for clang-tidy profiling data.
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.