clang-tools  7.0.0
ClangTidyDiagnosticConsumer.cpp
Go to the documentation of this file.
1 //===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 ///
10 /// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
11 /// and ClangTidyError classes.
12 ///
13 /// This tool uses the Clang Tooling infrastructure, see
14 /// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
15 /// for details on setting it up with LLVM source tree.
16 ///
17 //===----------------------------------------------------------------------===//
18 
20 #include "ClangTidyOptions.h"
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"
26 #include <tuple>
27 #include <vector>
28 using namespace clang;
29 using namespace tidy;
30 
31 namespace {
32 class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
33 public:
34  ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
35  DiagnosticOptions *DiagOpts,
36  ClangTidyError &Error)
37  : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
38 
39 protected:
40  void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,
41  DiagnosticsEngine::Level Level, StringRef Message,
42  ArrayRef<CharSourceRange> Ranges,
43  DiagOrStoredDiag Info) override {
44  // Remove check name from the message.
45  // FIXME: Remove this once there's a better way to pass check names than
46  // appending the check name to the message in ClangTidyContext::diag and
47  // using getCustomDiagID.
48  std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";
49  if (Message.endswith(CheckNameInMessage))
50  Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
51 
52  auto TidyMessage =
53  Loc.isValid()
54  ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc)
55  : tooling::DiagnosticMessage(Message);
56  if (Level == DiagnosticsEngine::Note) {
57  Error.Notes.push_back(TidyMessage);
58  return;
59  }
60  assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");
61  Error.Message = TidyMessage;
62  }
63 
64  void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
65  DiagnosticsEngine::Level Level,
66  ArrayRef<CharSourceRange> Ranges) override {}
67 
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.");
78 
79  tooling::Replacement Replacement(Loc.getManager(), Range,
80  FixIt.CodeToInsert);
81  llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
82  // FIXME: better error handling (at least, don't let other replacements be
83  // applied).
84  if (Err) {
85  llvm::errs() << "Fix conflicts with existing fix! "
86  << llvm::toString(std::move(Err)) << "\n";
87  assert(false && "Fix conflicts with existing fix!");
88  }
89  }
90  }
91 
92  void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {}
93 
94  void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
95  StringRef ModuleName) override {}
96 
97  void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
98  StringRef ModuleName) override {}
99 
100  void endDiagnostic(DiagOrStoredDiag D,
101  DiagnosticsEngine::Level Level) override {
102  assert(!Error.Message.Message.empty() && "Message has not been set");
103  }
104 
105 private:
106  ClangTidyError &Error;
107 };
108 } // end anonymous namespace
109 
110 ClangTidyError::ClangTidyError(StringRef CheckName,
111  ClangTidyError::Level DiagLevel,
112  StringRef BuildDirectory, bool IsWarningAsError)
113  : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
114  IsWarningAsError(IsWarningAsError) {}
115 
116 // Returns true if GlobList starts with the negative indicator ('-'), removes it
117 // from the GlobList.
118 static bool ConsumeNegativeIndicator(StringRef &GlobList) {
119  GlobList = GlobList.trim(" \r\n");
120  if (GlobList.startswith("-")) {
121  GlobList = GlobList.substr(1);
122  return true;
123  }
124  return false;
125 }
126 // Converts first glob from the comma-separated list of globs to Regex and
127 // removes it and the trailing comma from the GlobList.
128 static llvm::Regex ConsumeGlob(StringRef &GlobList) {
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) {
135  if (C == '*')
136  RegexText.push_back('.');
137  else if (MetaChars.find(C) != StringRef::npos)
138  RegexText.push_back('\\');
139  RegexText.push_back(C);
140  }
141  RegexText.push_back('$');
142  return llvm::Regex(RegexText);
143 }
144 
145 GlobList::GlobList(StringRef Globs)
146  : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)),
147  NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {}
148 
149 bool GlobList::contains(StringRef S, bool Contains) {
150  if (Regex.match(S))
151  Contains = Positive;
152 
153  if (NextGlob)
154  Contains = NextGlob->contains(S, Contains);
155  return Contains;
156 }
157 
159 public:
160  CachedGlobList(StringRef Globs) : Globs(Globs) {}
161 
162  bool contains(StringRef S) {
163  switch (auto &Result = Cache[S]) {
164  case Yes: return true;
165  case No: return false;
166  case None:
167  Result = Globs.contains(S) ? Yes : No;
168  return Result == Yes;
169  }
170  llvm_unreachable("invalid enum");
171  }
172 
173 private:
174  GlobList Globs;
175  enum Tristate { None, Yes, No };
176  llvm::StringMap<Tristate> Cache;
177 };
178 
180  std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
182  : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
183  Profile(false),
184  AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) {
185  // Before the first translation unit we can get errors related to command-line
186  // parsing, use empty string for the file name in this case.
187  setCurrentFile("");
188 }
189 
191 
192 DiagnosticBuilder ClangTidyContext::diag(
193  StringRef CheckName, SourceLocation Loc, StringRef Description,
194  DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
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);
200 }
201 
202 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
203  DiagEngine = Engine;
204 }
205 
206 void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
207  DiagEngine->setSourceManager(SourceMgr);
208 }
209 
210 void ClangTidyContext::setCurrentFile(StringRef File) {
211  CurrentFile = File;
212  CurrentOptions = getOptionsForFile(CurrentFile);
213  CheckFilter = llvm::make_unique<CachedGlobList>(*getOptions().Checks);
214  WarningAsErrorFilter =
215  llvm::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors);
216 }
217 
218 void ClangTidyContext::setASTContext(ASTContext *Context) {
219  DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
220  LangOpts = Context->getLangOpts();
221 }
222 
224  return OptionsProvider->getGlobalOptions();
225 }
226 
228  return CurrentOptions;
229 }
230 
232  // Merge options on top of getDefaults() as a safeguard against options with
233  // unset values.
235  OptionsProvider->getOptions(File));
236 }
237 
238 void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; }
239 
241  ProfilePrefix = Prefix;
242 }
243 
244 llvm::Optional<ClangTidyProfiling::StorageParams>
246  if (ProfilePrefix.empty())
247  return llvm::None;
248 
249  return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile);
250 }
251 
252 bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
253  assert(CheckFilter != nullptr);
254  return CheckFilter->contains(CheckName);
255 }
256 
257 bool ClangTidyContext::treatAsError(StringRef CheckName) const {
258  assert(WarningAsErrorFilter != nullptr);
259  return WarningAsErrorFilter->contains(CheckName);
260 }
261 
262 /// \brief Store a \c ClangTidyError.
263 void ClangTidyContext::storeError(const ClangTidyError &Error) {
264  Errors.push_back(Error);
265 }
266 
267 StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
268  llvm::DenseMap<unsigned, std::string>::const_iterator I =
269  CheckNamesByDiagnosticID.find(DiagnosticID);
270  if (I != CheckNamesByDiagnosticID.end())
271  return I->second;
272  return "";
273 }
274 
276  ClangTidyContext &Ctx, bool RemoveIncompatibleErrors)
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,
283  /*ShouldOwnClient=*/false);
284  Context.setDiagnosticsEngine(Diags.get());
285 }
286 
287 void ClangTidyDiagnosticConsumer::finalizeLastError() {
288  if (!Errors.empty()) {
289  ClangTidyError &Error = Errors.back();
290  if (!Context.isCheckEnabled(Error.DiagnosticName) &&
291  Error.DiagLevel != ClangTidyError::Error) {
292  ++Context.Stats.ErrorsIgnoredCheckFilter;
293  Errors.pop_back();
294  } else if (!LastErrorRelatesToUserCode) {
295  ++Context.Stats.ErrorsIgnoredNonUserCode;
296  Errors.pop_back();
297  } else if (!LastErrorPassesLineFilter) {
298  ++Context.Stats.ErrorsIgnoredLineFilter;
299  Errors.pop_back();
300  } else {
301  ++Context.Stats.ErrorsDisplayed;
302  }
303  }
304  LastErrorRelatesToUserCode = false;
305  LastErrorPassesLineFilter = false;
306 }
307 
308 static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
309  unsigned DiagID, const ClangTidyContext &Context) {
310  const size_t NolintIndex = Line.find(NolintDirectiveText);
311  if (NolintIndex == StringRef::npos)
312  return false;
313 
314  size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
315  // Check if the specific checks are specified in brackets.
316  if (BracketIndex < Line.size() && Line[BracketIndex] == '(') {
317  ++BracketIndex;
318  const size_t BracketEndIndex = Line.find(')', BracketIndex);
319  if (BracketEndIndex != StringRef::npos) {
320  StringRef ChecksStr =
321  Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
322  // Allow disabling all the checks with "*".
323  if (ChecksStr != "*") {
324  StringRef CheckName = Context.getCheckName(DiagID);
325  // Allow specifying a few check names, delimited with comma.
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();
331  }
332  }
333  }
334  return true;
335 }
336 
337 static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc,
338  unsigned DiagID,
339  const ClangTidyContext &Context) {
340  bool Invalid;
341  const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
342  if (Invalid)
343  return false;
344 
345  // Check if there's a NOLINT on this line.
346  const char *P = CharacterData;
347  while (*P != '\0' && *P != '\r' && *P != '\n')
348  ++P;
349  StringRef RestOfLine(CharacterData, P - CharacterData + 1);
350  if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context))
351  return true;
352 
353  // Check if there's a NOLINTNEXTLINE on the previous line.
354  const char *BufBegin =
355  SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
356  if (Invalid || P == BufBegin)
357  return false;
358 
359  // Scan backwards over the current line.
360  P = CharacterData;
361  while (P != BufBegin && *P != '\n')
362  --P;
363 
364  // If we reached the begin of the file there is no line before it.
365  if (P == BufBegin)
366  return false;
367 
368  // Skip over the newline.
369  --P;
370  const char *LineEnd = P;
371 
372  // Now we're on the previous line. Skip to the beginning of it.
373  while (P != BufBegin && *P != '\n')
374  --P;
375 
376  RestOfLine = StringRef(P, LineEnd - P + 1);
377  if (IsNOLINTFound("NOLINTNEXTLINE", RestOfLine, DiagID, Context))
378  return true;
379 
380  return false;
381 }
382 
383 static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc,
384  unsigned DiagID,
385  const ClangTidyContext &Context) {
386  while (true) {
387  if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context))
388  return true;
389  if (!Loc.isMacroID())
390  return false;
391  Loc = SM.getImmediateExpansionRange(Loc).getBegin();
392  }
393  return false;
394 }
395 
397  DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
398  if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
399  return;
400 
401  if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
402  DiagLevel != DiagnosticsEngine::Fatal &&
403  LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(),
404  Info.getLocation(), Info.getID(),
405  Context)) {
406  ++Context.Stats.ErrorsIgnoredNOLINT;
407  // Ignored a warning, should ignore related notes as well
408  LastErrorWasIgnored = true;
409  return;
410  }
411 
412  LastErrorWasIgnored = false;
413  // Count warnings/errors.
414  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
415 
416  if (DiagLevel == DiagnosticsEngine::Note) {
417  assert(!Errors.empty() &&
418  "A diagnostic note can only be appended to a message.");
419  } else {
420  finalizeLastError();
421  StringRef WarningOption =
422  Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
423  Info.getID());
424  std::string CheckName = !WarningOption.empty()
425  ? ("clang-diagnostic-" + WarningOption).str()
426  : Context.getCheckName(Info.getID()).str();
427 
428  if (CheckName.empty()) {
429  // This is a compiler diagnostic without a warning option. Assign check
430  // name based on its level.
431  switch (DiagLevel) {
432  case DiagnosticsEngine::Error:
433  case DiagnosticsEngine::Fatal:
434  CheckName = "clang-diagnostic-error";
435  break;
436  case DiagnosticsEngine::Warning:
437  CheckName = "clang-diagnostic-warning";
438  break;
439  default:
440  CheckName = "clang-diagnostic-unknown";
441  break;
442  }
443  }
444 
445  ClangTidyError::Level Level = ClangTidyError::Warning;
446  if (DiagLevel == DiagnosticsEngine::Error ||
447  DiagLevel == DiagnosticsEngine::Fatal) {
448  // Force reporting of Clang errors regardless of filters and non-user
449  // code.
450  Level = ClangTidyError::Error;
451  LastErrorRelatesToUserCode = true;
452  LastErrorPassesLineFilter = true;
453  }
454  bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
455  Context.treatAsError(CheckName);
456  Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
457  IsWarningAsError);
458  }
459 
460  ClangTidyDiagnosticRenderer Converter(
461  Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
462  Errors.back());
463  SmallString<100> Message;
464  Info.FormatDiagnostic(Message);
465  FullSourceLoc Loc =
466  (Info.getLocation().isInvalid())
467  ? FullSourceLoc()
468  : FullSourceLoc(Info.getLocation(), Info.getSourceManager());
469  Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
470  Info.getFixItHints());
471 
472  checkFilters(Info.getLocation());
473 }
474 
475 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
476  unsigned LineNumber) const {
477  if (Context.getGlobalOptions().LineFilter.empty())
478  return true;
479  for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
480  if (FileName.endswith(Filter.Name)) {
481  if (Filter.LineRanges.empty())
482  return true;
483  for (const FileFilter::LineRange &Range : Filter.LineRanges) {
484  if (Range.first <= LineNumber && LineNumber <= Range.second)
485  return true;
486  }
487  return false;
488  }
489  }
490  return false;
491 }
492 
493 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) {
494  // Invalid location may mean a diagnostic in a command line, don't skip these.
495  if (!Location.isValid()) {
496  LastErrorRelatesToUserCode = true;
497  LastErrorPassesLineFilter = true;
498  return;
499  }
500 
501  const SourceManager &Sources = Diags->getSourceManager();
502  if (!*Context.getOptions().SystemHeaders &&
503  Sources.isInSystemHeader(Location))
504  return;
505 
506  // FIXME: We start with a conservative approach here, but the actual type of
507  // location needed depends on the check (in particular, where this check wants
508  // to apply fixes).
509  FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
510  const FileEntry *File = Sources.getFileEntryForID(FID);
511 
512  // -DMACRO definitions on the command line have locations in a virtual buffer
513  // that doesn't have a FileEntry. Don't skip these as well.
514  if (!File) {
515  LastErrorRelatesToUserCode = true;
516  LastErrorPassesLineFilter = true;
517  return;
518  }
519 
520  StringRef FileName(File->getName());
521  LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
522  Sources.isInMainFile(Location) ||
523  getHeaderFilter()->match(FileName);
524 
525  unsigned LineNumber = Sources.getExpansionLineNumber(Location);
526  LastErrorPassesLineFilter =
527  LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
528 }
529 
530 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
531  if (!HeaderFilter)
532  HeaderFilter =
533  llvm::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
534  return HeaderFilter.get();
535 }
536 
537 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
538  SmallVectorImpl<ClangTidyError> &Errors) const {
539  // Each error is modelled as the set of intervals in which it applies
540  // replacements. To detect overlapping replacements, we use a sweep line
541  // algorithm over these sets of intervals.
542  // An event here consists of the opening or closing of an interval. During the
543  // process, we maintain a counter with the amount of open intervals. If we
544  // find an endpoint of an interval and this counter is different from 0, it
545  // means that this interval overlaps with another one, so we set it as
546  // inapplicable.
547  struct Event {
548  // An event can be either the begin or the end of an interval.
549  enum EventType {
550  ET_Begin = 1,
551  ET_End = -1,
552  };
553 
554  Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
555  unsigned ErrorSize)
556  : Type(Type), ErrorId(ErrorId) {
557  // The events are going to be sorted by their position. In case of draw:
558  //
559  // * If an interval ends at the same position at which other interval
560  // begins, this is not an overlapping, so we want to remove the ending
561  // interval before adding the starting one: end events have higher
562  // priority than begin events.
563  //
564  // * If we have several begin points at the same position, we will mark as
565  // inapplicable the ones that we process later, so the first one has to
566  // be the one with the latest end point, because this one will contain
567  // all the other intervals. For the same reason, if we have several end
568  // points in the same position, the last one has to be the one with the
569  // earliest begin point. In both cases, we sort non-increasingly by the
570  // position of the complementary.
571  //
572  // * In case of two equal intervals, the one whose error is bigger can
573  // potentially contain the other one, so we want to process its begin
574  // points before and its end points later.
575  //
576  // * Finally, if we have two equal intervals whose errors have the same
577  // size, none of them will be strictly contained inside the other.
578  // Sorting by ErrorId will guarantee that the begin point of the first
579  // one will be processed before, disallowing the second one, and the
580  // end point of the first one will also be processed before,
581  // disallowing the first one.
582  if (Type == ET_Begin)
583  Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
584  else
585  Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
586  }
587 
588  bool operator<(const Event &Other) const {
589  return Priority < Other.Priority;
590  }
591 
592  // Determines if this event is the begin or the end of an interval.
593  EventType Type;
594  // The index of the error to which the interval that generated this event
595  // belongs.
596  unsigned ErrorId;
597  // The events will be sorted based on this field.
598  std::tuple<unsigned, EventType, int, int, unsigned> Priority;
599  };
600 
601  // Compute error sizes.
602  std::vector<int> Sizes;
603  for (const auto &Error : Errors) {
604  int Size = 0;
605  for (const auto &FileAndReplaces : Error.Fix) {
606  for (const auto &Replace : FileAndReplaces.second)
607  Size += Replace.getLength();
608  }
609  Sizes.push_back(Size);
610  }
611 
612  // Build events from error intervals.
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();
620  // FIXME: Handle empty intervals, such as those from insertions.
621  if (Begin == End)
622  continue;
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]);
626  }
627  }
628  }
629 
630  std::vector<bool> Apply(Errors.size(), true);
631  for (auto &FileAndEvents : FileEvents) {
632  std::vector<Event> &Events = FileAndEvents.second;
633  // Sweep.
634  std::sort(Events.begin(), Events.end());
635  int OpenIntervals = 0;
636  for (const auto &Event : Events) {
637  if (Event.Type == Event::ET_End)
638  --OpenIntervals;
639  // This has to be checked after removing the interval from the count if it
640  // is an end event, or before adding it if it is a begin event.
641  if (OpenIntervals != 0)
642  Apply[Event.ErrorId] = false;
643  if (Event.Type == Event::ET_Begin)
644  ++OpenIntervals;
645  }
646  assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
647  }
648 
649  for (unsigned I = 0; I < Errors.size(); ++I) {
650  if (!Apply[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");
654  }
655  }
656 }
657 
658 namespace {
659 struct LessClangTidyError {
660  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
661  const tooling::DiagnosticMessage &M1 = LHS.Message;
662  const tooling::DiagnosticMessage &M2 = RHS.Message;
663 
664  return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
665  std::tie(M2.FilePath, M2.FileOffset, M2.Message);
666  }
667 };
668 struct EqualClangTidyError {
669  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
670  LessClangTidyError Less;
671  return !Less(LHS, RHS) && !Less(RHS, LHS);
672  }
673 };
674 } // end anonymous namespace
675 
676 // Flushes the internal diagnostics buffer to the ClangTidyContext.
678  finalizeLastError();
679 
680  std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
681  Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
682  Errors.end());
683 
684  if (RemoveIncompatibleErrors)
685  removeIncompatibleErrors(Errors);
686 
687  for (const ClangTidyError &Error : Errors)
688  Context.storeError(Error);
689  Errors.clear();
690 }
llvm::Optional< std::string > Checks
Checks filter.
SourceLocation Loc
&#39;#&#39; 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 &#39;*&#39; metacharacter is supported) with optional &#39;-&#39; p...
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
HeaderHandle File
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)
Contains options for clang-tidy.
static bool ConsumeNegativeIndicator(StringRef &GlobList)
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.
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.
PathRef FileName
llvm::Optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
FunctionInfo Info
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&#39;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)
Definition: Protocol.cpp:517
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)