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) {
73 CharSourceRange
Range = FixIt.RemoveRange;
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;
168 return Result == Yes;
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);
202 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
207 DiagEngine->setSourceManager(SourceMgr);
214 WarningAsErrorFilter =
219 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
220 LangOpts = Context->getLangOpts();
224 return OptionsProvider->getGlobalOptions();
228 return CurrentOptions;
235 OptionsProvider->getOptions(File));
241 ProfilePrefix = Prefix;
244 llvm::Optional<ClangTidyProfiling::StorageParams>
246 if (ProfilePrefix.empty())
253 assert(CheckFilter !=
nullptr);
254 return CheckFilter->contains(CheckName);
258 assert(WarningAsErrorFilter !=
nullptr);
259 return WarningAsErrorFilter->contains(CheckName);
264 Errors.push_back(Error);
268 llvm::DenseMap<unsigned, std::string>::const_iterator I =
269 CheckNamesByDiagnosticID.find(DiagnosticID);
270 if (I != CheckNamesByDiagnosticID.end())
277 : Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors),
278 LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
279 LastErrorWasIgnored(false) {
280 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts =
new DiagnosticOptions();
281 Diags = llvm::make_unique<DiagnosticsEngine>(
282 IntrusiveRefCntPtr<DiagnosticIDs>(
new DiagnosticIDs), &*DiagOpts,
this,
284 Context.setDiagnosticsEngine(Diags.get());
287 void ClangTidyDiagnosticConsumer::finalizeLastError() {
288 if (!Errors.empty()) {
291 Error.DiagLevel != ClangTidyError::Error) {
294 }
else if (!LastErrorRelatesToUserCode) {
297 }
else if (!LastErrorPassesLineFilter) {
304 LastErrorRelatesToUserCode =
false;
305 LastErrorPassesLineFilter =
false;
310 const size_t NolintIndex = Line.find(NolintDirectiveText);
311 if (NolintIndex == StringRef::npos)
314 size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
316 if (BracketIndex < Line.size() && Line[BracketIndex] ==
'(') {
318 const size_t BracketEndIndex = Line.find(
')', BracketIndex);
319 if (BracketEndIndex != StringRef::npos) {
320 StringRef ChecksStr =
321 Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
323 if (ChecksStr !=
"*") {
326 SmallVector<StringRef, 1>
Checks;
327 ChecksStr.split(Checks,
',', -1,
false);
328 llvm::transform(Checks, Checks.begin(),
329 [](StringRef S) {
return S.trim(); });
330 return llvm::find(Checks, CheckName) != Checks.end();
341 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
346 const char *P = CharacterData;
347 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
349 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
354 const char *BufBegin =
355 SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
356 if (Invalid || P == BufBegin)
361 while (P != BufBegin && *P !=
'\n')
370 const char *LineEnd = P;
373 while (P != BufBegin && *P !=
'\n')
376 RestOfLine = StringRef(P, LineEnd - P + 1);
377 if (
IsNOLINTFound(
"NOLINTNEXTLINE", RestOfLine, DiagID, Context))
389 if (!Loc.isMacroID())
391 Loc = SM.getImmediateExpansionRange(Loc).getBegin();
397 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
398 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
401 if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
402 DiagLevel != DiagnosticsEngine::Fatal &&
404 Info.getLocation(), Info.getID(),
408 LastErrorWasIgnored =
true;
412 LastErrorWasIgnored =
false;
414 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
416 if (DiagLevel == DiagnosticsEngine::Note) {
417 assert(!Errors.empty() &&
418 "A diagnostic note can only be appended to a message.");
421 StringRef WarningOption =
422 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
424 std::string CheckName = !WarningOption.empty()
425 ? (
"clang-diagnostic-" + WarningOption).str()
428 if (CheckName.empty()) {
432 case DiagnosticsEngine::Error:
433 case DiagnosticsEngine::Fatal:
434 CheckName =
"clang-diagnostic-error";
436 case DiagnosticsEngine::Warning:
437 CheckName =
"clang-diagnostic-warning";
440 CheckName =
"clang-diagnostic-unknown";
445 ClangTidyError::Level Level = ClangTidyError::Warning;
446 if (DiagLevel == DiagnosticsEngine::Error ||
447 DiagLevel == DiagnosticsEngine::Fatal) {
450 Level = ClangTidyError::Error;
451 LastErrorRelatesToUserCode =
true;
452 LastErrorPassesLineFilter =
true;
454 bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
460 ClangTidyDiagnosticRenderer Converter(
461 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
464 Info.FormatDiagnostic(Message);
466 (Info.getLocation().isInvalid())
468 : FullSourceLoc(Info.getLocation(), Info.getSourceManager());
469 Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
470 Info.getFixItHints());
472 checkFilters(Info.getLocation());
475 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef
FileName,
476 unsigned LineNumber)
const {
480 if (FileName.endswith(Filter.Name)) {
481 if (Filter.LineRanges.empty())
484 if (Range.first <= LineNumber && LineNumber <= Range.second)
493 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location) {
495 if (!Location.isValid()) {
496 LastErrorRelatesToUserCode =
true;
497 LastErrorPassesLineFilter =
true;
501 const SourceManager &Sources = Diags->getSourceManager();
503 Sources.isInSystemHeader(Location))
509 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
510 const FileEntry *
File = Sources.getFileEntryForID(FID);
515 LastErrorRelatesToUserCode =
true;
516 LastErrorPassesLineFilter =
true;
520 StringRef
FileName(File->getName());
521 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
522 Sources.isInMainFile(Location) ||
523 getHeaderFilter()->match(FileName);
525 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
526 LastErrorPassesLineFilter =
527 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
530 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
534 return HeaderFilter.get();
537 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
538 SmallVectorImpl<ClangTidyError> &Errors)
const {
554 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
556 : Type(Type), ErrorId(ErrorId) {
582 if (Type == ET_Begin)
583 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
585 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
588 bool operator<(
const Event &Other)
const {
589 return Priority < Other.Priority;
598 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
602 std::vector<int> Sizes;
603 for (
const auto &Error : Errors) {
605 for (
const auto &FileAndReplaces : Error.Fix) {
606 for (
const auto &Replace : FileAndReplaces.second)
607 Size += Replace.getLength();
609 Sizes.push_back(Size);
613 std::map<std::string, std::vector<Event>> FileEvents;
614 for (
unsigned I = 0; I < Errors.size(); ++I) {
615 for (
const auto &FileAndReplace : Errors[I].
Fix) {
616 for (
const auto &Replace : FileAndReplace.second) {
617 unsigned Begin = Replace.getOffset();
618 unsigned End = Begin + Replace.getLength();
619 const std::string &FilePath = Replace.getFilePath();
623 auto &Events = FileEvents[FilePath];
624 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
625 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
630 std::vector<bool> Apply(Errors.size(),
true);
631 for (
auto &FileAndEvents : FileEvents) {
632 std::vector<Event> &Events = FileAndEvents.second;
634 std::sort(Events.begin(), Events.end());
635 int OpenIntervals = 0;
636 for (
const auto &Event : Events) {
637 if (Event.Type == Event::ET_End)
641 if (OpenIntervals != 0)
642 Apply[Event.ErrorId] =
false;
643 if (Event.Type == Event::ET_Begin)
646 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
649 for (
unsigned I = 0; I < Errors.size(); ++I) {
651 Errors[I].Fix.clear();
652 Errors[I].Notes.emplace_back(
653 "this fix will not be applied because it overlaps with another fix");
659 struct LessClangTidyError {
661 const tooling::DiagnosticMessage &M1 = LHS.Message;
662 const tooling::DiagnosticMessage &M2 = RHS.Message;
664 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
665 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
668 struct EqualClangTidyError {
670 LessClangTidyError Less;
671 return !Less(LHS, RHS) && !Less(RHS, LHS);
680 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
681 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
684 if (RemoveIncompatibleErrors)
685 removeIncompatibleErrors(Errors);
688 Context.storeError(Error);
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.
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
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.
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
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.
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)
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.
static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
tooling::ExecutionContext * Ctx
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.
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, bool RemoveIncompatibleErrors=true)
bool operator<(const CompletionItem &L, const CompletionItem &R)
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)