21 #include "clang/AST/ASTDiagnostic.h" 22 #include "clang/Basic/DiagnosticOptions.h" 23 #include "clang/Frontend/DiagnosticRenderer.h" 24 #include "llvm/ADT/STLExtras.h" 25 #include "llvm/ADT/SmallString.h" 28 using namespace clang;
32 class ClangTidyDiagnosticRenderer :
public DiagnosticRenderer {
34 ClangTidyDiagnosticRenderer(
const LangOptions &LangOpts,
35 DiagnosticOptions *DiagOpts,
37 : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
40 void emitDiagnosticMessage(FullSourceLoc
Loc, PresumedLoc PLoc,
41 DiagnosticsEngine::Level Level, StringRef
Message,
42 ArrayRef<CharSourceRange> Ranges,
43 DiagOrStoredDiag
Info)
override {
48 std::string CheckNameInMessage =
" [" + Error.DiagnosticName +
"]";
49 if (Message.endswith(CheckNameInMessage))
50 Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
54 ? tooling::DiagnosticMessage(Message, Loc.getManager(),
Loc)
55 : tooling::DiagnosticMessage(Message);
56 if (Level == DiagnosticsEngine::Note) {
57 Error.Notes.push_back(TidyMessage);
60 assert(Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
61 Error.Message = TidyMessage;
64 void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
65 DiagnosticsEngine::Level Level,
66 ArrayRef<CharSourceRange> Ranges)
override {}
68 void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
69 SmallVectorImpl<CharSourceRange> &Ranges,
70 ArrayRef<FixItHint>
Hints)
override {
71 assert(Loc.isValid());
72 for (
const auto &
FixIt : Hints) {
74 assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
75 "Invalid range in the fix-it hint.");
76 assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
77 "Only file locations supported in fix-it hints.");
79 tooling::Replacement Replacement(Loc.getManager(),
Range,
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(FullSourceLoc Loc, PresumedLoc PLoc)
override {}
94 void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
95 StringRef ModuleName)
override {}
97 void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
98 StringRef ModuleName)
override {}
100 void endDiagnostic(DiagOrStoredDiag
D,
101 DiagnosticsEngine::Level Level)
override {
102 assert(!Error.Message.Message.empty() &&
"Message has not been set");
111 ClangTidyError::Level DiagLevel,
112 StringRef BuildDirectory,
bool IsWarningAsError)
113 : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
114 IsWarningAsError(IsWarningAsError) {}
119 GlobList = GlobList.trim(
" \r\n");
120 if (GlobList.startswith(
"-")) {
121 GlobList = GlobList.substr(1);
129 StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(
','));
130 StringRef Glob = UntrimmedGlob.trim(
' ');
131 GlobList = GlobList.substr(UntrimmedGlob.size() + 1);
132 SmallString<128> RegexText(
"^");
133 StringRef MetaChars(
"()^$|*+?.[]\\{}");
134 for (
char C : Glob) {
136 RegexText.push_back(
'.');
137 else if (MetaChars.find(C) != StringRef::npos)
138 RegexText.push_back(
'\\');
139 RegexText.push_back(C);
141 RegexText.push_back(
'$');
142 return llvm::Regex(RegexText);
147 NextGlob(Globs.empty() ? nullptr : new
GlobList(Globs)) {}
154 Contains = NextGlob->contains(S, Contains);
163 switch (
auto &
Result = Cache[S]) {
164 case Yes:
return true;
165 case No:
return false;
167 Result = Globs.contains(S) ? Yes : No;
170 llvm_unreachable(
"invalid enum");
175 enum Tristate { None, Yes, No };
176 llvm::StringMap<Tristate> Cache;
180 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
182 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
184 AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) {
193 StringRef CheckName, SourceLocation Loc, StringRef
Description,
194 DiagnosticIDs::Level Level ) {
195 assert(Loc.isValid());
196 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
197 Level, (Description +
" [" + CheckName +
"]").str());
198 CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
199 return DiagEngine->Report(Loc, ID);
203 DiagEngine->setSourceManager(SourceMgr);
210 WarningAsErrorFilter =
215 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
216 LangOpts = Context->getLangOpts();
220 return OptionsProvider->getGlobalOptions();
224 return CurrentOptions;
231 OptionsProvider->getOptions(File));
237 ProfilePrefix = Prefix;
240 llvm::Optional<ClangTidyProfiling::StorageParams>
242 if (ProfilePrefix.empty())
249 assert(CheckFilter !=
nullptr);
250 return CheckFilter->contains(CheckName);
254 assert(WarningAsErrorFilter !=
nullptr);
255 return WarningAsErrorFilter->contains(CheckName);
259 llvm::DenseMap<unsigned, std::string>::const_iterator I =
260 CheckNamesByDiagnosticID.find(DiagnosticID);
261 if (I != CheckNamesByDiagnosticID.end())
268 : Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors),
269 LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
270 LastErrorWasIgnored(false) {}
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 size_t NolintIndex = Line.find(NolintDirectiveText);
296 if (NolintIndex == StringRef::npos)
299 size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
301 if (BracketIndex < Line.size() && Line[BracketIndex] ==
'(') {
303 const size_t BracketEndIndex = Line.find(
')', BracketIndex);
304 if (BracketEndIndex != StringRef::npos) {
305 StringRef ChecksStr =
306 Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
308 if (ChecksStr !=
"*") {
311 SmallVector<StringRef, 1>
Checks;
312 ChecksStr.split(Checks,
',', -1,
false);
313 llvm::transform(Checks, Checks.begin(),
314 [](StringRef S) {
return S.trim(); });
315 return llvm::find(Checks, CheckName) != Checks.end();
326 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
331 const char *P = CharacterData;
332 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
334 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
339 const char *BufBegin =
340 SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
341 if (Invalid || P == BufBegin)
346 while (P != BufBegin && *P !=
'\n')
355 const char *LineEnd = P;
358 while (P != BufBegin && *P !=
'\n')
361 RestOfLine = StringRef(P, LineEnd - P + 1);
362 if (
IsNOLINTFound(
"NOLINTNEXTLINE", RestOfLine, DiagID, Context))
369 SourceLocation Loc,
unsigned DiagID,
374 if (!Loc.isMacroID())
376 Loc = SM.getImmediateExpansionRange(Loc).getBegin();
382 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
383 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
386 if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
387 DiagLevel != DiagnosticsEngine::Fatal &&
389 Info.getLocation(), Info.getID(),
393 LastErrorWasIgnored =
true;
397 LastErrorWasIgnored =
false;
399 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
401 if (DiagLevel == DiagnosticsEngine::Note) {
402 assert(!Errors.empty() &&
403 "A diagnostic note can only be appended to a message.");
406 StringRef WarningOption =
407 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
409 std::string CheckName = !WarningOption.empty()
410 ? (
"clang-diagnostic-" + WarningOption).str()
413 if (CheckName.empty()) {
417 case DiagnosticsEngine::Error:
418 case DiagnosticsEngine::Fatal:
419 CheckName =
"clang-diagnostic-error";
421 case DiagnosticsEngine::Warning:
422 CheckName =
"clang-diagnostic-warning";
425 CheckName =
"clang-diagnostic-unknown";
430 ClangTidyError::Level Level = ClangTidyError::Warning;
431 if (DiagLevel == DiagnosticsEngine::Error ||
432 DiagLevel == DiagnosticsEngine::Fatal) {
435 Level = ClangTidyError::Error;
436 LastErrorRelatesToUserCode =
true;
437 LastErrorPassesLineFilter =
true;
439 bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
445 ClangTidyDiagnosticRenderer Converter(
446 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
449 Info.FormatDiagnostic(Message);
451 if (Info.getLocation().isValid() && Info.hasSourceManager())
452 Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());
453 Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
454 Info.getFixItHints());
456 if (Info.hasSourceManager())
457 checkFilters(Info.getLocation(), Info.getSourceManager());
460 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef
FileName,
461 unsigned LineNumber)
const {
465 if (FileName.endswith(Filter.Name)) {
466 if (Filter.LineRanges.empty())
469 if (Range.first <= LineNumber && LineNumber <= Range.second)
478 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location,
479 const SourceManager &Sources) {
481 if (!Location.isValid()) {
482 LastErrorRelatesToUserCode =
true;
483 LastErrorPassesLineFilter =
true;
488 Sources.isInSystemHeader(Location))
494 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
495 const FileEntry *File = Sources.getFileEntryForID(FID);
500 LastErrorRelatesToUserCode =
true;
501 LastErrorPassesLineFilter =
true;
505 StringRef
FileName(File->getName());
506 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
507 Sources.isInMainFile(Location) ||
508 getHeaderFilter()->match(FileName);
510 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
511 LastErrorPassesLineFilter =
512 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
515 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
519 return HeaderFilter.get();
522 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
538 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
540 : Type(Type), ErrorId(ErrorId) {
566 if (Type == ET_Begin)
567 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
569 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
572 bool operator<(
const Event &Other)
const {
573 return Priority < Other.Priority;
582 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
586 std::vector<int> Sizes;
587 for (
const auto &Error : Errors) {
589 for (
const auto &FileAndReplaces : Error.Fix) {
590 for (
const auto &Replace : FileAndReplaces.second)
591 Size += Replace.getLength();
593 Sizes.push_back(Size);
597 std::map<std::string, std::vector<Event>> FileEvents;
598 for (
unsigned I = 0; I < Errors.size(); ++I) {
599 for (
const auto &FileAndReplace : Errors[I].
Fix) {
600 for (
const auto &Replace : FileAndReplace.second) {
601 unsigned Begin = Replace.getOffset();
602 unsigned End = Begin + Replace.getLength();
603 const std::string &FilePath = Replace.getFilePath();
607 auto &Events = FileEvents[FilePath];
608 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
609 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
614 std::vector<bool> Apply(Errors.size(),
true);
615 for (
auto &FileAndEvents : FileEvents) {
616 std::vector<Event> &Events = FileAndEvents.second;
618 std::sort(Events.begin(), Events.end());
619 int OpenIntervals = 0;
620 for (
const auto &Event : Events) {
621 if (Event.Type == Event::ET_End)
625 if (OpenIntervals != 0)
626 Apply[Event.ErrorId] =
false;
627 if (Event.Type == Event::ET_Begin)
630 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
633 for (
unsigned I = 0; I < Errors.size(); ++I) {
635 Errors[I].Fix.clear();
636 Errors[I].Notes.emplace_back(
637 "this fix will not be applied because it overlaps with another fix");
643 struct LessClangTidyError {
645 const tooling::DiagnosticMessage &M1 = LHS.Message;
646 const tooling::DiagnosticMessage &M2 = RHS.Message;
648 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
649 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
652 struct EqualClangTidyError {
654 LessClangTidyError Less;
655 return !Less(LHS, RHS) && !Less(RHS, LHS);
663 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
664 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
666 if (RemoveIncompatibleErrors)
667 removeIncompatibleErrors();
668 return std::move(Errors);
llvm::Optional< std::string > Checks
Checks filter.
SourceLocation Loc
'#' location in the include directive
ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const
Creates a new ClangTidyOptions instance combined from all fields of this instance overridden by the f...
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...
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
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.
bool operator<(const SymbolLocation::Position &L, const SymbolLocation::Position &R)
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.
static cl::opt< bool > AllowEnablingAnalyzerAlphaCheckers("allow-enabling-analyzer-alpha-checkers", cl::init(false), cl::Hidden, cl::cat(ClangTidyCategory))
This option allows enabling the experimental alpha checkers from the static analyzer.
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
std::vector< ClangTidyError > take()
llvm::Optional< ClangTidyProfiling::StorageParams > getProfileStorageParams() const
const LangOptions & getLangOpts() const
Gets the language options from the AST context.
ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, bool IsWarningAsError)
StringRef getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
ClangTidyOptions getOptionsForFile(StringRef File) const
Returns options for File.
const ClangTidyOptions & getOptions() const
Returns options for CurrentFile.
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.
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
void setProfileStoragePrefix(StringRef ProfilePrefix)
Control storage of profile date.
unsigned ErrorsIgnoredLineFilter
llvm::Optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
CachedGlobList(StringRef Globs)
Contains a list of line ranges in a single file.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
ClangTidyContext(std::unique_ptr< ClangTidyOptionsProvider > OptionsProvider, bool AllowEnablingAnalyzerAlphaCheckers=false)
Initializes ClangTidyContext instance.
static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line, unsigned DiagID, const ClangTidyContext &Context)
std::vector< FixItHint > Hints
CharSourceRange Range
SourceRange for the file name.
A detected error complete with information to display diagnostic and automatic fix.
static cl::opt< std::string > Checks("checks", cl::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))
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
llvm::Optional< FixItHint > FixIt
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
bool treatAsError(StringRef CheckName) const
Returns true if the check should be upgraded to error for the CurrentFile.
void setEnableProfiling(bool Profile)
Control profile collection in clang-tidy.
static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, bool RemoveIncompatibleErrors=true)
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.