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.DiagnosticName +
"]";
49 if (Message.endswith(CheckNameInMessage))
50 Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
52 auto TidyMessage = Loc.isValid()
53 ? tooling::DiagnosticMessage(Message, *SM, 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(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 tooling::Replacement Replacement(SM, Range, FixIt.CodeToInsert);
81 llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
85 llvm::errs() <<
"Fix conflicts with existing fix! "
87 assert(
false &&
"Fix conflicts with existing fix!");
92 void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc,
93 const SourceManager &SM)
override {}
95 void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc,
97 const SourceManager &SM)
override {}
99 void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc,
100 StringRef ModuleName,
101 const SourceManager &SM)
override {}
103 void endDiagnostic(DiagOrStoredDiag D,
104 DiagnosticsEngine::Level Level)
override {
105 assert(!Error.Message.Message.empty() &&
"Message has not been set");
114 ClangTidyError::Level DiagLevel,
115 StringRef BuildDirectory,
bool IsWarningAsError)
116 : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
117 IsWarningAsError(IsWarningAsError) {}
122 if (GlobList.startswith(
"-")) {
123 GlobList = GlobList.substr(1);
131 StringRef Glob = GlobList.substr(0, GlobList.find(
',')).trim();
132 GlobList = GlobList.substr(Glob.size() + 1);
133 SmallString<128> RegexText(
"^");
134 StringRef MetaChars(
"()^$|*+?.[]\\{}");
135 for (
char C : Glob) {
137 RegexText.push_back(
'.');
138 else if (MetaChars.find(C) != StringRef::npos)
139 RegexText.push_back(
'\\');
140 RegexText.push_back(C);
142 RegexText.push_back(
'$');
143 return llvm::Regex(RegexText);
148 NextGlob(Globs.empty() ? nullptr : new
GlobList(Globs)) {}
155 Contains = NextGlob->contains(S, Contains);
160 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
161 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
169 StringRef CheckName, SourceLocation Loc, StringRef Description,
170 DiagnosticIDs::Level Level ) {
171 assert(Loc.isValid());
172 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
173 Level, (Description +
" [" + CheckName +
"]").str());
174 CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
175 return DiagEngine->Report(Loc, ID);
178 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
183 DiagEngine->setSourceManager(SourceMgr);
194 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
195 LangOpts = Context->getLangOpts();
199 return OptionsProvider->getGlobalOptions();
203 return CurrentOptions;
210 OptionsProvider->getOptions(File));
216 assert(CheckFilter !=
nullptr);
221 assert(WarningAsErrorFilter !=
nullptr);
222 return *WarningAsErrorFilter;
227 Errors.push_back(Error);
231 llvm::DenseMap<unsigned, std::string>::const_iterator I =
232 CheckNamesByDiagnosticID.find(DiagnosticID);
233 if (I != CheckNamesByDiagnosticID.end())
239 :
Context(Ctx), LastErrorRelatesToUserCode(false),
240 LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {
241 IntrusiveRefCntPtr<DiagnosticOptions>
DiagOpts =
new DiagnosticOptions();
242 Diags.reset(
new DiagnosticsEngine(
243 IntrusiveRefCntPtr<DiagnosticIDs>(
new DiagnosticIDs), &*DiagOpts,
this,
245 Context.setDiagnosticsEngine(Diags.get());
248 void ClangTidyDiagnosticConsumer::finalizeLastError() {
249 if (!Errors.empty()) {
252 Error.DiagLevel != ClangTidyError::Error) {
255 }
else if (!LastErrorRelatesToUserCode) {
258 }
else if (!LastErrorPassesLineFilter) {
265 LastErrorRelatesToUserCode =
false;
266 LastErrorPassesLineFilter =
false;
271 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
273 const char *P = CharacterData;
274 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
276 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
278 if (RestOfLine.find(
"NOLINT") != StringRef::npos) {
286 SourceLocation Loc) {
290 if (!Loc.isMacroID())
292 Loc = SM.getImmediateExpansionRange(Loc).first;
298 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
299 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
302 if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
303 DiagLevel != DiagnosticsEngine::Fatal &&
305 Info.getLocation())) {
308 LastErrorWasIgnored =
true;
312 LastErrorWasIgnored =
false;
314 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
316 if (DiagLevel == DiagnosticsEngine::Note) {
317 assert(!Errors.empty() &&
318 "A diagnostic note can only be appended to a message.");
321 StringRef WarningOption =
322 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
324 std::string CheckName = !WarningOption.empty()
325 ? (
"clang-diagnostic-" + WarningOption).str()
328 if (CheckName.empty()) {
332 case DiagnosticsEngine::Error:
333 case DiagnosticsEngine::Fatal:
334 CheckName =
"clang-diagnostic-error";
336 case DiagnosticsEngine::Warning:
337 CheckName =
"clang-diagnostic-warning";
340 CheckName =
"clang-diagnostic-unknown";
345 ClangTidyError::Level Level = ClangTidyError::Warning;
346 if (DiagLevel == DiagnosticsEngine::Error ||
347 DiagLevel == DiagnosticsEngine::Fatal) {
350 Level = ClangTidyError::Error;
351 LastErrorRelatesToUserCode =
true;
352 LastErrorPassesLineFilter =
true;
354 bool IsWarningAsError =
355 DiagLevel == DiagnosticsEngine::Warning &&
361 ClangTidyDiagnosticRenderer Converter(
362 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
365 Info.FormatDiagnostic(Message);
366 SourceManager *Sources =
nullptr;
367 if (Info.hasSourceManager())
368 Sources = &Info.getSourceManager();
369 Converter.emitDiagnostic(Info.getLocation(), DiagLevel,
Message,
370 Info.getRanges(), Info.getFixItHints(), Sources);
372 checkFilters(Info.getLocation());
375 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
376 unsigned LineNumber)
const {
380 if (FileName.endswith(Filter.Name)) {
381 if (Filter.LineRanges.empty())
384 if (Range.first <= LineNumber && LineNumber <= Range.second)
393 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location) {
395 if (!Location.isValid()) {
396 LastErrorRelatesToUserCode =
true;
397 LastErrorPassesLineFilter =
true;
401 const SourceManager &Sources = Diags->getSourceManager();
403 Sources.isInSystemHeader(Location))
409 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
410 const FileEntry *
File = Sources.getFileEntryForID(FID);
415 LastErrorRelatesToUserCode =
true;
416 LastErrorPassesLineFilter =
true;
420 StringRef FileName(File->getName());
421 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
422 Sources.isInMainFile(Location) ||
423 getHeaderFilter()->match(FileName);
425 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
426 LastErrorPassesLineFilter =
427 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
430 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
434 return HeaderFilter.get();
437 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
438 SmallVectorImpl<ClangTidyError> &Errors)
const {
454 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
456 : Type(Type), ErrorId(ErrorId) {
482 if (Type == ET_Begin)
483 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
485 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
488 bool operator<(
const Event &Other)
const {
489 return Priority < Other.Priority;
498 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
502 std::vector<int> Sizes;
503 for (
const auto &Error : Errors) {
505 for (
const auto &FileAndReplaces : Error.Fix) {
506 for (
const auto &Replace : FileAndReplaces.second)
507 Size += Replace.getLength();
509 Sizes.push_back(Size);
513 std::map<std::string, std::vector<Event>> FileEvents;
514 for (
unsigned I = 0; I < Errors.size(); ++I) {
515 for (
const auto &FileAndReplace : Errors[I].
Fix) {
516 for (
const auto &Replace : FileAndReplace.second) {
517 unsigned Begin = Replace.getOffset();
518 unsigned End = Begin + Replace.getLength();
519 const std::string &FilePath = Replace.getFilePath();
523 auto &Events = FileEvents[FilePath];
524 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
525 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
530 std::vector<bool> Apply(Errors.size(),
true);
531 for (
auto &FileAndEvents : FileEvents) {
532 std::vector<Event> &Events = FileAndEvents.second;
534 std::sort(Events.begin(), Events.end());
535 int OpenIntervals = 0;
536 for (
const auto &Event : Events) {
537 if (Event.Type == Event::ET_End)
541 if (OpenIntervals != 0)
542 Apply[Event.ErrorId] =
false;
543 if (Event.Type == Event::ET_Begin)
546 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
549 for (
unsigned I = 0; I < Errors.size(); ++I) {
551 Errors[I].Fix.clear();
552 Errors[I].Notes.emplace_back(
553 "this fix will not be applied because it overlaps with another fix");
559 struct LessClangTidyError {
561 const tooling::DiagnosticMessage &M1 = LHS.Message;
562 const tooling::DiagnosticMessage &M2 = RHS.Message;
564 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
565 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
568 struct EqualClangTidyError {
570 LessClangTidyError Less;
571 return !Less(LHS, RHS) && !Less(RHS, LHS);
580 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
581 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
583 removeIncompatibleErrors(Errors);
586 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.
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
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.
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
GlobList & getWarningAsErrorFilter()
Returns check filter for the CurrentFile which selects checks for upgrade to error.
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc)
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.
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.