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(SourceLocation
Loc, PresumedLoc PLoc,
40 DiagnosticsEngine::Level Level, StringRef Message,
41 ArrayRef<CharSourceRange> Ranges,
42 const SourceManager *
SM,
43 DiagOrStoredDiag Info)
override {
48 std::string CheckNameInMessage =
" [" + Error.
CheckName +
"]";
49 if (Message.endswith(CheckNameInMessage))
50 Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
55 if (Level == DiagnosticsEngine::Note) {
56 Error.Notes.push_back(TidyMessage);
59 assert(Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
63 void emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc,
64 DiagnosticsEngine::Level Level,
65 ArrayRef<CharSourceRange> Ranges,
66 const SourceManager &SM)
override {}
68 void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level,
69 SmallVectorImpl<CharSourceRange> &Ranges,
70 ArrayRef<FixItHint> Hints,
71 const SourceManager &SM)
override {
72 assert(Loc.isValid());
73 for (
const auto &FixIt : Hints) {
74 CharSourceRange
Range = FixIt.RemoveRange;
75 assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
76 "Invalid range in the fix-it hint.");
77 assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
78 "Only file locations supported in fix-it hints.");
80 Error.Fix.insert(tooling::Replacement(SM, Range, FixIt.CodeToInsert));
84 void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc,
85 const SourceManager &SM)
override {}
87 void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc,
89 const SourceManager &SM)
override {}
91 void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc,
93 const SourceManager &SM)
override {}
95 void endDiagnostic(DiagOrStoredDiag D,
96 DiagnosticsEngine::Level Level)
override {
97 assert(!Error.Message.Message.empty() &&
"Message has not been set");
106 : Message(Message), FileOffset(0) {}
109 const SourceManager &Sources,
112 assert(Loc.isValid() && Loc.isFileID());
113 FilePath = Sources.getFilename(Loc);
119 : CheckName(CheckName), DiagLevel(DiagLevel) {}
124 if (GlobList.startswith(
"-")) {
125 GlobList = GlobList.substr(1);
133 StringRef Glob = GlobList.substr(0, GlobList.find(
',')).trim();
134 GlobList = GlobList.substr(Glob.size() + 1);
135 SmallString<128> RegexText(
"^");
136 StringRef MetaChars(
"()^$|*+?.[]\\{}");
137 for (
char C : Glob) {
139 RegexText.push_back(
'.');
140 else if (MetaChars.find(C) != StringRef::npos)
141 RegexText.push_back(
'\\');
142 RegexText.push_back(C);
144 RegexText.push_back(
'$');
145 return llvm::Regex(RegexText);
150 NextGlob(Globs.empty() ? nullptr : new
GlobList(Globs)) {}
157 Contains = NextGlob->contains(S, Contains);
162 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
163 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
171 StringRef CheckName, SourceLocation Loc, StringRef Description,
172 DiagnosticIDs::Level Level ) {
173 assert(Loc.isValid());
175 const char *CharacterData =
176 DiagEngine->getSourceManager().getCharacterData(Loc, &Invalid);
178 const char *P = CharacterData;
179 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
181 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
183 if (RestOfLine.find(
"NOLINT") != StringRef::npos) {
184 Level = DiagnosticIDs::Ignored;
188 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
189 Level, (Description +
" [" + CheckName +
"]").str());
190 if (CheckNamesByDiagnosticID.count(ID) == 0)
191 CheckNamesByDiagnosticID.insert(std::make_pair(ID, CheckName.str()));
192 return DiagEngine->Report(Loc, ID);
195 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
200 DiagEngine->setSourceManager(SourceMgr);
210 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
211 LangOpts = Context->getLangOpts();
215 return OptionsProvider->getGlobalOptions();
219 return CurrentOptions;
226 OptionsProvider->getOptions(CurrentFile));
232 assert(CheckFilter !=
nullptr);
237 return CheckFilter->contains(CheckName);
242 Errors.push_back(Error);
246 llvm::DenseMap<unsigned, std::string>::const_iterator I =
247 CheckNamesByDiagnosticID.find(DiagnosticID);
248 if (I != CheckNamesByDiagnosticID.end())
254 :
Context(Ctx), LastErrorRelatesToUserCode(false),
255 LastErrorPassesLineFilter(false) {
256 IntrusiveRefCntPtr<DiagnosticOptions>
DiagOpts =
new DiagnosticOptions();
257 Diags.reset(
new DiagnosticsEngine(
258 IntrusiveRefCntPtr<DiagnosticIDs>(
new DiagnosticIDs), &*DiagOpts,
this,
260 Context.setDiagnosticsEngine(Diags.get());
263 void ClangTidyDiagnosticConsumer::finalizeLastError() {
264 if (!Errors.empty()) {
270 }
else if (!LastErrorRelatesToUserCode) {
273 }
else if (!LastErrorPassesLineFilter) {
280 LastErrorRelatesToUserCode =
false;
281 LastErrorPassesLineFilter =
false;
285 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
287 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
289 if (DiagLevel == DiagnosticsEngine::Note) {
290 assert(!Errors.empty() &&
291 "A diagnostic note can only be appended to a message.");
294 StringRef WarningOption =
295 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
297 std::string CheckName = !WarningOption.empty()
298 ? (
"clang-diagnostic-" + WarningOption).str()
301 if (CheckName.empty()) {
305 case DiagnosticsEngine::Error:
306 case DiagnosticsEngine::Fatal:
307 CheckName =
"clang-diagnostic-error";
309 case DiagnosticsEngine::Warning:
310 CheckName =
"clang-diagnostic-warning";
313 CheckName =
"clang-diagnostic-unknown";
319 if (DiagLevel == DiagnosticsEngine::Error ||
320 DiagLevel == DiagnosticsEngine::Fatal) {
324 LastErrorRelatesToUserCode =
true;
325 LastErrorPassesLineFilter =
true;
332 ClangTidyDiagnosticRenderer Converter(
333 LangOpts, &Context.DiagEngine->getDiagnosticOptions(), Errors.back());
334 SmallString<100> Message;
335 Info.FormatDiagnostic(Message);
336 SourceManager *Sources =
nullptr;
337 if (Info.hasSourceManager())
338 Sources = &Info.getSourceManager();
339 Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message,
340 Info.getRanges(), Info.getFixItHints(), Sources);
342 checkFilters(Info.getLocation());
345 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
346 unsigned LineNumber)
const {
350 if (FileName.endswith(Filter.Name)) {
351 if (Filter.LineRanges.empty())
354 if (Range.first <= LineNumber && LineNumber <= Range.second)
363 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location) {
365 if (!Location.isValid()) {
366 LastErrorRelatesToUserCode =
true;
367 LastErrorPassesLineFilter =
true;
371 const SourceManager &Sources = Diags->getSourceManager();
373 Sources.isInSystemHeader(Location))
379 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
380 const FileEntry *
File = Sources.getFileEntryForID(FID);
385 LastErrorRelatesToUserCode =
true;
386 LastErrorPassesLineFilter =
true;
390 StringRef FileName(File->getName());
391 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
392 Sources.isInMainFile(Location) ||
393 getHeaderFilter()->match(FileName);
395 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
396 LastErrorPassesLineFilter =
397 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
400 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
404 return HeaderFilter.get();
407 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
408 SmallVectorImpl<ClangTidyError> &Errors)
const {
424 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
426 : Type(Type), ErrorId(ErrorId) {
452 if (Type == ET_Begin)
453 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
455 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
458 bool operator<(
const Event &Other)
const {
459 return Priority < Other.Priority;
468 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
472 std::vector<int> Sizes;
473 for (
const auto &Error : Errors) {
475 for (
const auto &Replace : Error.
Fix)
476 Size += Replace.getLength();
477 Sizes.push_back(Size);
481 std::map<std::string, std::vector<Event>> FileEvents;
482 for (
unsigned I = 0; I < Errors.size(); ++I) {
483 for (
const auto &Replace : Errors[I].
Fix) {
484 unsigned Begin = Replace.getOffset();
485 unsigned End = Begin + Replace.getLength();
486 const std::string &FilePath = Replace.getFilePath();
490 FileEvents[FilePath].push_back(
491 Event(Begin, End, Event::ET_Begin, I, Sizes[I]));
492 FileEvents[FilePath].push_back(
493 Event(Begin, End, Event::ET_End, I, Sizes[I]));
497 std::vector<bool> Apply(Errors.size(),
true);
498 for (
auto &FileAndEvents : FileEvents) {
499 std::vector<Event> &Events = FileAndEvents.second;
501 std::sort(Events.begin(), Events.end());
502 int OpenIntervals = 0;
503 for (
const auto &Event : Events) {
504 if (Event.Type == Event::ET_End)
508 if (OpenIntervals != 0)
509 Apply[Event.ErrorId] =
false;
510 if (Event.Type == Event::ET_Begin)
513 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
516 for (
unsigned I = 0; I < Errors.size(); ++I) {
518 Errors[I].Fix.clear();
519 Errors[I].Notes.push_back(
521 " it overlaps with another fix"));
527 struct LessClangTidyError {
536 struct EqualClangTidyError {
538 LessClangTidyError Less;
539 return !Less(LHS, RHS) && !Less(RHS, LHS);
548 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
549 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
551 removeIncompatibleErrors(Errors);
554 Context.storeError(Error);
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...
std::vector< std::unique_ptr< ClangTidyCheck > > Checks
void finish() override
Flushes the internal diagnostics buffer to the ClangTidyContext.
bool contains(StringRef S)
Returns true if the pattern matches S.
A message from a clang-tidy check.
llvm::Optional< std::string > HeaderFilterRegex
Output warnings from headers matching this filter.
ClangTidyMessage(StringRef Message="")
Contains options for clang-tidy.
unsigned ErrorsIgnoredCheckFilter
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx)
static bool ConsumeNegativeIndicator(StringRef &GlobList)
unsigned ErrorsIgnoredNOLINT
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.
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
ClangTidyError(StringRef CheckName, Level DiagLevel)
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
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 ClangTidyOptions getDefaults()
These options are used for all settings that haven't been overridden by the OptionsProvider.
tooling::Replacements Fix
static cl::opt< bool > Fix("fix", cl::desc("Apply suggested fixes. Without -fix-errors\n""clang-tidy will bail out if any compilation\n""errors were found."), cl::init(false), cl::cat(ClangTidyCategory))
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check name is enabled for the CurrentFile.
GlobList & getChecksFilter()
Returns check filter for the CurrentFile.
Container for clang-tidy profiling data.