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 bool IsWarningAsError,
120 StringRef BuildDirectory)
121 : CheckName(CheckName), BuildDirectory(BuildDirectory), DiagLevel(DiagLevel),
122 IsWarningAsError(IsWarningAsError) {}
127 if (GlobList.startswith(
"-")) {
128 GlobList = GlobList.substr(1);
136 StringRef Glob = GlobList.substr(0, GlobList.find(
',')).trim();
137 GlobList = GlobList.substr(Glob.size() + 1);
138 SmallString<128> RegexText(
"^");
139 StringRef MetaChars(
"()^$|*+?.[]\\{}");
140 for (
char C : Glob) {
142 RegexText.push_back(
'.');
143 else if (MetaChars.find(C) != StringRef::npos)
144 RegexText.push_back(
'\\');
145 RegexText.push_back(C);
147 RegexText.push_back(
'$');
148 return llvm::Regex(RegexText);
153 NextGlob(Globs.empty() ? nullptr : new
GlobList(Globs)) {}
160 Contains = NextGlob->contains(S, Contains);
165 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
166 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
174 StringRef CheckName, SourceLocation Loc, StringRef Description,
175 DiagnosticIDs::Level Level ) {
176 assert(Loc.isValid());
177 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
178 Level, (Description +
" [" + CheckName +
"]").str());
179 if (CheckNamesByDiagnosticID.count(ID) == 0)
180 CheckNamesByDiagnosticID.insert(std::make_pair(ID, CheckName.str()));
181 return DiagEngine->Report(Loc, ID);
184 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
189 DiagEngine->setSourceManager(SourceMgr);
200 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
201 LangOpts = Context->getLangOpts();
205 return OptionsProvider->getGlobalOptions();
209 return CurrentOptions;
216 OptionsProvider->getOptions(File));
222 assert(CheckFilter !=
nullptr);
227 assert(WarningAsErrorFilter !=
nullptr);
228 return *WarningAsErrorFilter;
233 Errors.push_back(Error);
237 llvm::DenseMap<unsigned, std::string>::const_iterator I =
238 CheckNamesByDiagnosticID.find(DiagnosticID);
239 if (I != CheckNamesByDiagnosticID.end())
245 :
Context(Ctx), LastErrorRelatesToUserCode(false),
246 LastErrorPassesLineFilter(false) {
247 IntrusiveRefCntPtr<DiagnosticOptions>
DiagOpts =
new DiagnosticOptions();
248 Diags.reset(
new DiagnosticsEngine(
249 IntrusiveRefCntPtr<DiagnosticIDs>(
new DiagnosticIDs), &*DiagOpts,
this,
251 Context.setDiagnosticsEngine(Diags.get());
254 void ClangTidyDiagnosticConsumer::finalizeLastError() {
255 if (!Errors.empty()) {
261 }
else if (!LastErrorRelatesToUserCode) {
264 }
else if (!LastErrorPassesLineFilter) {
271 LastErrorRelatesToUserCode =
false;
272 LastErrorPassesLineFilter =
false;
277 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
279 const char *P = CharacterData;
280 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
282 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
284 if (RestOfLine.find(
"NOLINT") != StringRef::npos) {
292 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
293 if (Info.getLocation().isValid() &&
294 DiagLevel != DiagnosticsEngine::Error &&
295 DiagLevel != DiagnosticsEngine::Fatal &&
301 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
303 if (DiagLevel == DiagnosticsEngine::Note) {
304 assert(!Errors.empty() &&
305 "A diagnostic note can only be appended to a message.");
308 StringRef WarningOption =
309 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
311 std::string CheckName = !WarningOption.empty()
312 ? (
"clang-diagnostic-" + WarningOption).str()
315 if (CheckName.empty()) {
319 case DiagnosticsEngine::Error:
320 case DiagnosticsEngine::Fatal:
321 CheckName =
"clang-diagnostic-error";
323 case DiagnosticsEngine::Warning:
324 CheckName =
"clang-diagnostic-warning";
327 CheckName =
"clang-diagnostic-unknown";
333 if (DiagLevel == DiagnosticsEngine::Error ||
334 DiagLevel == DiagnosticsEngine::Fatal) {
338 LastErrorRelatesToUserCode =
true;
339 LastErrorPassesLineFilter =
true;
341 bool IsWarningAsError =
342 DiagLevel == DiagnosticsEngine::Warning &&
344 Errors.push_back(
ClangTidyError(CheckName, Level, IsWarningAsError,
348 ClangTidyDiagnosticRenderer Converter(
349 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
351 SmallString<100> Message;
352 Info.FormatDiagnostic(Message);
353 SourceManager *Sources =
nullptr;
354 if (Info.hasSourceManager())
355 Sources = &Info.getSourceManager();
356 Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message,
357 Info.getRanges(), Info.getFixItHints(), Sources);
359 checkFilters(Info.getLocation());
362 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
363 unsigned LineNumber)
const {
367 if (FileName.endswith(Filter.Name)) {
368 if (Filter.LineRanges.empty())
371 if (Range.first <= LineNumber && LineNumber <= Range.second)
380 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location) {
382 if (!Location.isValid()) {
383 LastErrorRelatesToUserCode =
true;
384 LastErrorPassesLineFilter =
true;
388 const SourceManager &Sources = Diags->getSourceManager();
390 Sources.isInSystemHeader(Location))
396 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
397 const FileEntry *
File = Sources.getFileEntryForID(FID);
402 LastErrorRelatesToUserCode =
true;
403 LastErrorPassesLineFilter =
true;
407 StringRef FileName(File->getName());
408 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
409 Sources.isInMainFile(Location) ||
410 getHeaderFilter()->match(FileName);
412 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
413 LastErrorPassesLineFilter =
414 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
417 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
421 return HeaderFilter.get();
424 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
425 SmallVectorImpl<ClangTidyError> &Errors)
const {
441 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
443 : Type(Type), ErrorId(ErrorId) {
469 if (Type == ET_Begin)
470 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
472 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
475 bool operator<(
const Event &Other)
const {
476 return Priority < Other.Priority;
485 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
489 std::vector<int> Sizes;
490 for (
const auto &Error : Errors) {
492 for (
const auto &Replace : Error.
Fix)
493 Size += Replace.getLength();
494 Sizes.push_back(Size);
498 std::map<std::string, std::vector<Event>> FileEvents;
499 for (
unsigned I = 0; I < Errors.size(); ++I) {
500 for (
const auto &Replace : Errors[I].
Fix) {
501 unsigned Begin = Replace.getOffset();
502 unsigned End = Begin + Replace.getLength();
503 const std::string &FilePath = Replace.getFilePath();
507 FileEvents[FilePath].push_back(
508 Event(Begin, End, Event::ET_Begin, I, Sizes[I]));
509 FileEvents[FilePath].push_back(
510 Event(Begin, End, Event::ET_End, I, Sizes[I]));
514 std::vector<bool> Apply(Errors.size(),
true);
515 for (
auto &FileAndEvents : FileEvents) {
516 std::vector<Event> &Events = FileAndEvents.second;
518 std::sort(Events.begin(), Events.end());
519 int OpenIntervals = 0;
520 for (
const auto &Event : Events) {
521 if (Event.Type == Event::ET_End)
525 if (OpenIntervals != 0)
526 Apply[Event.ErrorId] =
false;
527 if (Event.Type == Event::ET_Begin)
530 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
533 for (
unsigned I = 0; I < Errors.size(); ++I) {
535 Errors[I].Fix.clear();
536 Errors[I].Notes.push_back(
538 " it overlaps with another fix"));
544 struct LessClangTidyError {
553 struct EqualClangTidyError {
555 LessClangTidyError Less;
556 return !Less(LHS, RHS) && !Less(RHS, LHS);
565 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
566 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
568 removeIncompatibleErrors(Errors);
571 Context.storeError(Error);
SourceLocation Loc
'#' location in the include directive
ClangTidyError(StringRef CheckName, Level DiagLevel, bool IsWarningAsError, StringRef BuildDirectory)
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
GlobList & getWarningAsErrorFilter()
Returns check filter for the CurrentFile which selects checks for upgrade to error.
unsigned WarningsAsErrors
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.
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)
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(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
GlobList & getChecksFilter()
Returns check filter for the CurrentFile.
Container for clang-tidy profiling data.
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.