[clang-tools-extra] r244586 - Add an IncludeInserter to clang-tidy.

Manuel Klimek via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 11 04:37:49 PDT 2015


Author: klimek
Date: Tue Aug 11 06:37:48 2015
New Revision: 244586

URL: http://llvm.org/viewvc/llvm-project?rev=244586&view=rev
Log:
Add an IncludeInserter to clang-tidy.

Will be used to allow checks to insert includes at the right position.

Added:
    clang-tools-extra/trunk/clang-tidy/IncludeInserter.cpp
    clang-tools-extra/trunk/clang-tidy/IncludeInserter.h
    clang-tools-extra/trunk/clang-tidy/IncludeSorter.cpp
    clang-tools-extra/trunk/clang-tidy/IncludeSorter.h
    clang-tools-extra/trunk/unittests/clang-tidy/IncludeInserterTest.cpp
Modified:
    clang-tools-extra/trunk/clang-tidy/CMakeLists.txt
    clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt
    clang-tools-extra/trunk/unittests/clang-tidy/ClangTidyTest.h

Modified: clang-tools-extra/trunk/clang-tidy/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/CMakeLists.txt?rev=244586&r1=244585&r2=244586&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clang-tidy/CMakeLists.txt Tue Aug 11 06:37:48 2015
@@ -7,6 +7,8 @@ add_clang_library(clangTidy
   ClangTidyModule.cpp
   ClangTidyDiagnosticConsumer.cpp
   ClangTidyOptions.cpp
+  IncludeInserter.cpp
+  IncludeSorter.cpp
 
   DEPENDS
   ClangSACheckers

Added: clang-tools-extra/trunk/clang-tidy/IncludeInserter.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/IncludeInserter.cpp?rev=244586&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/IncludeInserter.cpp (added)
+++ clang-tools-extra/trunk/clang-tidy/IncludeInserter.cpp Tue Aug 11 06:37:48 2015
@@ -0,0 +1,77 @@
+//===-------- IncludeInserter.cpp - clang-tidy ----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "IncludeInserter.h"
+
+namespace clang {
+namespace tidy {
+
+class IncludeInserterCallback : public PPCallbacks {
+public:
+  explicit IncludeInserterCallback(IncludeInserter *IncludeInserter)
+      : IncludeInserter(IncludeInserter) {}
+  // Implements PPCallbacks::InclusionDerective(). Records the names and source
+  // locations of the inclusions in the main source file being processed.
+  void InclusionDirective(SourceLocation HashLocation,
+                          const Token & /*include_token*/,
+                          StringRef FileNameRef, bool IsAngled,
+                          CharSourceRange FileNameRange,
+                          const FileEntry * /*IncludedFile*/,
+                          StringRef /*SearchPath*/, StringRef /*RelativePath*/,
+                          const Module * /*ImportedModule*/) override {
+    IncludeInserter->AddInclude(FileNameRef, IsAngled, HashLocation,
+                                FileNameRange.getEnd());
+  }
+
+private:
+  IncludeInserter *IncludeInserter;
+};
+
+IncludeInserter::IncludeInserter(const SourceManager &SourceMgr,
+                                 const LangOptions &LangOpts,
+                                 IncludeSorter::IncludeStyle Style)
+    : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style) {}
+
+IncludeInserter::~IncludeInserter() {}
+
+std::unique_ptr<PPCallbacks> IncludeInserter::CreatePPCallbacks() {
+  return llvm::make_unique<IncludeInserterCallback>(this);
+}
+
+llvm::Optional<FixItHint>
+IncludeInserter::CreateIncludeInsertion(FileID FileID, StringRef Header,
+                                        bool IsAngled) {
+  // We assume the same Header will never be included both angled and not
+  // angled.
+  if (!InsertedHeaders.insert(std::make_pair(FileID, std::set<std::string>()))
+           .second) {
+    return llvm::None;
+  }
+  if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) {
+    return llvm::None;
+  }
+  return IncludeSorterByFile[FileID]->CreateIncludeInsertion(Header, IsAngled);
+}
+
+void IncludeInserter::AddInclude(StringRef file_name, bool IsAngled,
+                                 SourceLocation HashLocation,
+                                 SourceLocation end_location) {
+  FileID FileID = SourceMgr.getFileID(HashLocation);
+  if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) {
+    IncludeSorterByFile.insert(std::make_pair(
+        FileID, llvm::make_unique<IncludeSorter>(
+                    &SourceMgr, &LangOpts, FileID,
+                    SourceMgr.getFilename(HashLocation), Style)));
+  }
+  IncludeSorterByFile[FileID]->AddInclude(file_name, IsAngled, HashLocation,
+                                          end_location);
+}
+
+} // namespace tidy
+} // namespace clang

Added: clang-tools-extra/trunk/clang-tidy/IncludeInserter.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/IncludeInserter.h?rev=244586&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/IncludeInserter.h (added)
+++ clang-tools-extra/trunk/clang-tidy/IncludeInserter.h Tue Aug 11 06:37:48 2015
@@ -0,0 +1,75 @@
+//===---------- IncludeInserter.h - clang-tidy ----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H
+
+#include "IncludeSorter.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/PPCallbacks.h"
+#include <memory>
+#include <string>
+
+namespace clang {
+namespace tidy {
+
+// IncludeInserter can be used by ClangTidyChecks in the following fashion:
+// class MyCheck : public ClangTidyCheck {
+//  public:
+//   void registerPPCallbacks(CompilerInstance& Compiler) override {
+//     Inserter.reset(new IncludeInserter(&Compiler.getSourceManager(),
+//                                        &Compiler.getLangOpts()));
+//     Compiler.getPreprocessor().addPPCallbacks(
+//         Inserter->CreatePPCallback());
+//   }
+//
+//   void registerMatchers(ast_matchers::MatchFinder* Finder) override { ... }
+//
+//   void check(const ast_matchers::MatchFinder::MatchResult& Result) override {
+//     ...
+//     Inserter->CreateIncludeInsertion(
+//         Result.SourceManager->getMainFileID(), "path/to/Header.h",
+//         /*IsAngled=*/false);
+//     ...
+//   }
+//
+//  private:
+//   std::unique_ptr<IncludeInserter> Inserter;
+// };
+class IncludeInserter {
+public:
+  IncludeInserter(const SourceManager &SourceMgr, const LangOptions &LangOpts,
+                  IncludeSorter::IncludeStyle Style);
+  ~IncludeInserter();
+
+  // Create PPCallbacks for registration with the compiler's preprocessor.
+  std::unique_ptr<PPCallbacks> CreatePPCallbacks();
+
+  // Creates a Header inclusion directive fixit. Returns None on error or
+  // if inclusion directive already exists.
+  llvm::Optional<FixItHint>
+  CreateIncludeInsertion(FileID FileID, llvm::StringRef Header, bool IsAngled);
+
+private:
+  void AddInclude(StringRef file_name, bool IsAngled,
+                  SourceLocation hash_location, SourceLocation end_location);
+
+  llvm::DenseMap<FileID, std::unique_ptr<IncludeSorter>> IncludeSorterByFile;
+  llvm::DenseMap<FileID, std::set<std::string>> InsertedHeaders;
+  const SourceManager &SourceMgr;
+  const LangOptions &LangOpts;
+  const IncludeSorter::IncludeStyle Style;
+  friend class IncludeInserterCallback;
+};
+
+} // namespace tidy
+} // namespace clang
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H

Added: clang-tools-extra/trunk/clang-tidy/IncludeSorter.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/IncludeSorter.cpp?rev=244586&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/IncludeSorter.cpp (added)
+++ clang-tools-extra/trunk/clang-tidy/IncludeSorter.cpp Tue Aug 11 06:37:48 2015
@@ -0,0 +1,267 @@
+//===---------- IncludeSorter.cpp - clang-tidy ----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "IncludeSorter.h"
+#include "clang/Lex/Lexer.h"
+
+namespace clang {
+namespace tidy {
+
+namespace {
+
+StringRef RemoveFirstSuffix(StringRef Str, ArrayRef<const char *> Suffixes) {
+  for (StringRef Suffix : Suffixes) {
+    if (Str.endswith(Suffix)) {
+      return Str.substr(0, Str.size() - Suffix.size());
+    }
+  }
+  return Str;
+}
+
+StringRef MakeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) {
+  // The list of suffixes to remove from source file names to get the
+  // "canonical" file names.
+  // E.g. tools/sort_includes.cc and tools/sort_includes_test.cc
+  // would both canonicalize to tools/sort_includes and tools/sort_includes.h
+  // (once canonicalized) will match as being the main include file associated
+  // with the source files.
+  if (Style == IncludeSorter::IS_LLVM) {
+    return RemoveFirstSuffix(
+        RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), {"Test"});
+  }
+  return RemoveFirstSuffix(
+      RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}),
+      {"_unittest", "_regtest", "_test"});
+}
+
+// Scan to the end of the line and return the offset of the next line.
+size_t FindNextLine(const char *Text) {
+  size_t EOLIndex = std::strcspn(Text, "\n");
+  return Text[EOLIndex] == '\0' ? EOLIndex : EOLIndex + 1;
+}
+
+IncludeSorter::IncludeKinds
+DetermineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile,
+                     bool IsAngled, IncludeSorter::IncludeStyle Style) {
+  // Compute the two "canonical" forms of the include's filename sans extension.
+  // The first form is the include's filename without ".h" or "-inl.h" at the
+  // end. The second form is the first form with "/public/" in the file path
+  // replaced by "/internal/".
+  if (IsAngled) {
+    // If the system include (<foo>) ends with ".h", then it is a normal C-style
+    // include. Otherwise assume it is a C++-style extensionless include.
+    return IncludeFile.endswith(".h") ? IncludeSorter::IK_CSystemInclude
+                                      : IncludeSorter::IK_CXXSystemInclude;
+  }
+  StringRef CanonicalInclude = MakeCanonicalName(IncludeFile, Style);
+  if (CanonicalFile.equals(CanonicalInclude)) {
+    return IncludeSorter::IK_MainTUInclude;
+  }
+  if (Style == IncludeSorter::IS_Google) {
+    std::pair<StringRef, StringRef> Parts = CanonicalInclude.split("/public/");
+    std::string AltCanonicalInclude =
+        Parts.first.str() + "/internal/" + Parts.second.str();
+    std::string ProtoCanonicalInclude =
+        Parts.first.str() + "/proto/" + Parts.second.str();
+
+    // Determine the kind of this inclusion.
+    if (CanonicalFile.equals(AltCanonicalInclude) ||
+        CanonicalFile.equals(ProtoCanonicalInclude)) {
+      return IncludeSorter::IK_MainTUInclude;
+    }
+  }
+  return IncludeSorter::IK_NonSystemInclude;
+}
+
+} // namespace
+
+IncludeSorter::IncludeSorter(const SourceManager *SourceMgr,
+                             const LangOptions *LangOpts, const FileID FileID,
+                             StringRef FileName, IncludeStyle Style)
+    : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style),
+      CurrentFileID(FileID), CanonicalFile(MakeCanonicalName(FileName, Style)) {
+}
+
+void IncludeSorter::AddInclude(StringRef FileName, bool IsAngled,
+                               SourceLocation HashLocation,
+                               SourceLocation EndLocation) {
+  int Offset = FindNextLine(SourceMgr->getCharacterData(EndLocation));
+
+  // Record the relevant location information for this inclusion directive.
+  IncludeLocations[FileName].push_back(
+      SourceRange(HashLocation, EndLocation.getLocWithOffset(Offset)));
+  SourceLocations.push_back(IncludeLocations[FileName].back());
+
+  // Stop if this inclusion is a duplicate.
+  if (IncludeLocations[FileName].size() > 1)
+    return;
+
+  // Add the included file's name to the appropriate bucket.
+  IncludeKinds Kind =
+      DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style);
+  if (Kind != IK_InvalidInclude)
+    IncludeBucket[Kind].push_back(FileName.str());
+}
+
+Optional<FixItHint> IncludeSorter::CreateIncludeInsertion(StringRef FileName,
+                                                          bool IsAngled) {
+  if (SourceLocations.empty()) {
+    return None;
+  }
+  std::string IncludeStmt =
+      IsAngled ? llvm::Twine("#include <" + FileName + ">\n").str()
+               : llvm::Twine("#include \"" + FileName + "\"\n").str();
+  auto IncludeKind =
+      DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style);
+  if (!IncludeBucket[IncludeKind].empty()) {
+    for (const std::string &IncludeEntry : IncludeBucket[IncludeKind]) {
+      if (FileName < IncludeEntry) {
+        const auto &Location = IncludeLocations[IncludeEntry][0];
+        return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt);
+      } else if (FileName == IncludeEntry) {
+        return llvm::None;
+      }
+    }
+    // FileName comes after all include entries in bucket, insert it after
+    // last.
+    const std::string &LastInclude = IncludeBucket[IncludeKind].back();
+    SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
+    return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(),
+                                      IncludeStmt);
+  }
+  IncludeKinds NonEmptyKind = IK_InvalidInclude;
+  for (int i = IncludeKind - 1; i >= 0; --i) {
+    if (!IncludeBucket[i].empty()) {
+      NonEmptyKind = static_cast<IncludeKinds>(i);
+      break;
+    }
+  }
+  if (NonEmptyKind == IK_InvalidInclude) {
+    return llvm::None;
+  }
+  const std::string &LastInclude = IncludeBucket[NonEmptyKind].back();
+  SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
+  IncludeStmt.append("\n");
+  return FixItHint::CreateInsertion(
+      LastIncludeLocation.getEnd().getLocWithOffset(1), IncludeStmt);
+}
+
+std::vector<FixItHint> IncludeSorter::GetEdits() {
+  if (SourceLocations.empty())
+    return {};
+
+  typedef std::map<int, std::pair<SourceRange, std::string>>
+      FileLineToSourceEditMap;
+  FileLineToSourceEditMap Edits;
+  auto SourceLocationIterator = SourceLocations.begin();
+  auto SourceLocationIteratorEnd = SourceLocations.end();
+
+  // Compute the Edits that need to be done to each line to add, replace, or
+  // delete inclusions.
+  for (int IncludeKind = 0; IncludeKind < IK_InvalidInclude; ++IncludeKind) {
+    std::sort(IncludeBucket[IncludeKind].begin(),
+              IncludeBucket[IncludeKind].end(),
+              [](const std::string &Left, const std::string &Right) {
+                return llvm::StringRef(Left).compare_lower(Right) < 0;
+              });
+    for (const auto &IncludeEntry : IncludeBucket[IncludeKind]) {
+      auto &Location = IncludeLocations[IncludeEntry];
+      SourceRangeVector::iterator LocationIterator = Location.begin();
+      SourceRangeVector::iterator LocationIteratorEnd = Location.end();
+      SourceRange FirstLocation = *LocationIterator;
+
+      // If the first occurrence of a particular include is on the current
+      // source line we are examining, leave it alone.
+      if (FirstLocation == *SourceLocationIterator)
+        ++LocationIterator;
+
+      // Add the deletion Edits for any (remaining) instances of this inclusion,
+      // and remove their Locations from the source Locations to be processed.
+      for (; LocationIterator != LocationIteratorEnd; ++LocationIterator) {
+        int LineNumber =
+            SourceMgr->getSpellingLineNumber(LocationIterator->getBegin());
+        Edits[LineNumber] = std::make_pair(*LocationIterator, "");
+        SourceLocationIteratorEnd =
+            std::remove(SourceLocationIterator, SourceLocationIteratorEnd,
+                        *LocationIterator);
+      }
+
+      if (FirstLocation == *SourceLocationIterator) {
+        // Do nothing except move to the next source Location (Location of an
+        // inclusion in the original, unchanged source file).
+        ++SourceLocationIterator;
+        continue;
+      }
+
+      // Add (or append to) the replacement text for this line in source file.
+      int LineNumber =
+          SourceMgr->getSpellingLineNumber(SourceLocationIterator->getBegin());
+      if (Edits.find(LineNumber) == Edits.end()) {
+        Edits[LineNumber].first =
+            SourceRange(SourceLocationIterator->getBegin());
+      }
+      StringRef SourceText = Lexer::getSourceText(
+          CharSourceRange::getCharRange(FirstLocation), *SourceMgr, *LangOpts);
+      Edits[LineNumber].second.append(SourceText.data(), SourceText.size());
+    }
+
+    // Clear the bucket.
+    IncludeBucket[IncludeKind].clear();
+  }
+
+  // Go through the single-line Edits and combine them into blocks of Edits.
+  int CurrentEndLine = 0;
+  SourceRange CurrentRange;
+  std::string CurrentText;
+  std::vector<FixItHint> Fixes;
+  for (const auto &LineEdit : Edits) {
+    const SourceRange &EditRange = LineEdit.second.first;
+    // If the current edit is on the next line after the previous edit, add it
+    // to the current block edit.
+    if (LineEdit.first == CurrentEndLine + 1 &&
+        CurrentRange.getBegin() != CurrentRange.getEnd()) {
+      if (EditRange.getBegin() != EditRange.getEnd()) {
+        ++CurrentEndLine;
+        CurrentRange.setEnd(EditRange.getEnd());
+      }
+      CurrentText += LineEdit.second.second;
+      // Otherwise report the current block edit and start a new block.
+    } else {
+      if (CurrentEndLine) {
+        Fixes.push_back(CreateFixIt(CurrentRange, CurrentText));
+      }
+
+      CurrentEndLine = LineEdit.first;
+      CurrentRange = EditRange;
+      CurrentText = LineEdit.second.second;
+    }
+  }
+  // Finally, report the current block edit if there is one.
+  if (CurrentEndLine) {
+    Fixes.push_back(CreateFixIt(CurrentRange, CurrentText));
+  }
+
+  // Reset the remaining internal state.
+  SourceLocations.clear();
+  IncludeLocations.clear();
+  return Fixes;
+}
+
+// Creates a fix-it for the given replacements.
+// Takes the the source location that will be replaced, and the new text.
+FixItHint IncludeSorter::CreateFixIt(SourceRange EditRange,
+                                     const std::string &NewText) {
+  FixItHint Fix;
+  Fix.RemoveRange = CharSourceRange::getCharRange(EditRange);
+  Fix.CodeToInsert = NewText;
+  return Fix;
+}
+
+} // namespace tidy
+} // namespace clang

Added: clang-tools-extra/trunk/clang-tidy/IncludeSorter.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/IncludeSorter.h?rev=244586&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/IncludeSorter.h (added)
+++ clang-tools-extra/trunk/clang-tidy/IncludeSorter.h Tue Aug 11 06:37:48 2015
@@ -0,0 +1,87 @@
+//===------------ IncludeSorter.h - clang-tidy ----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H
+
+#include "ClangTidy.h"
+#include <string>
+
+namespace clang {
+namespace tidy {
+
+// Class used by IncludeSorterCallback and IncludeInserterCallback to record the
+// names of the inclusions in a given source file being processed and generate
+// the necessary commands to sort the inclusions according to the precedence
+// enocded in IncludeKinds.
+class IncludeSorter {
+ public:
+  // Supported include styles.
+  enum IncludeStyle {
+    IS_LLVM = 0,
+    IS_Google = 1
+  };
+
+  // The classifications of inclusions, in the order they should be sorted.
+  enum IncludeKinds {
+    IK_MainTUInclude = 0,     // e.g. #include "foo.h" when editing foo.cc
+    IK_CSystemInclude = 1,    // e.g. #include <stdio.h>
+    IK_CXXSystemInclude = 2,  // e.g. #include <vector>
+    IK_NonSystemInclude = 3,  // e.g. #include "bar.h"
+    IK_InvalidInclude = 4     // total number of valid IncludeKinds
+  };
+
+  // IncludeSorter constructor; takes the FileID and name of the file to be
+  // processed by the sorter.
+  IncludeSorter(const SourceManager* SourceMgr,
+                const LangOptions* LangOpts, const FileID FileID,
+                StringRef FileName, IncludeStyle Style);
+
+  // Returns the SourceManager-specific file ID for the file being handled by
+  // the sorter.
+  const FileID current_FileID() const { return CurrentFileID; }
+
+  // Adds the given #include to the sorter.
+  void AddInclude(StringRef FileName, bool IsAngled,
+                  SourceLocation HashLocation, SourceLocation EndLocation);
+
+  // Returns the edits needed to sort the current set of includes and reset the
+  // internal state (so that different blocks of includes are sorted separately
+  // within the same file).
+  std::vector<FixItHint> GetEdits();
+
+  // Creates a quoted inclusion directive in the right sort order. Returns None
+  // on error or if header inclusion directive for header already exists.
+  Optional<FixItHint> CreateIncludeInsertion(StringRef FileName,
+                                             bool IsAngled);
+
+ private:
+  typedef SmallVector<SourceRange, 1> SourceRangeVector;
+
+  // Creates a fix-it for the given replacements.
+  // Takes the the source location that will be replaced, and the new text.
+  FixItHint CreateFixIt(SourceRange EditRange, const std::string& NewText);
+
+  const SourceManager* SourceMgr;
+  const LangOptions* LangOpts;
+  const IncludeStyle Style;
+  FileID CurrentFileID;
+  // The file name stripped of common suffixes.
+  StringRef CanonicalFile;
+  // Locations of visited include directives.
+  SourceRangeVector SourceLocations;
+  // Mapping from file name to #include locations.
+  llvm::StringMap<SourceRangeVector> IncludeLocations;
+  // Includes sorted into buckets.
+  SmallVector<std::string, 1> IncludeBucket[IK_InvalidInclude];
+};
+
+}  // namespace tidy
+}  // namespace clang
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H

Modified: clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt?rev=244586&r1=244585&r2=244586&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt Tue Aug 11 06:37:48 2015
@@ -9,6 +9,7 @@ include_directories(${CLANG_LINT_SOURCE_
 add_extra_unittest(ClangTidyTests
   ClangTidyDiagnosticConsumerTest.cpp
   ClangTidyOptionsTest.cpp
+  IncludeInserterTest.cpp
   GoogleModuleTest.cpp
   LLVMModuleTest.cpp
   MiscModuleTest.cpp

Modified: clang-tools-extra/trunk/unittests/clang-tidy/ClangTidyTest.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/ClangTidyTest.h?rev=244586&r1=244585&r2=244586&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-tidy/ClangTidyTest.h (original)
+++ clang-tools-extra/trunk/unittests/clang-tidy/ClangTidyTest.h Tue Aug 11 06:37:48 2015
@@ -17,6 +17,7 @@
 #include "clang/Frontend/FrontendActions.h"
 #include "clang/Tooling/Refactoring.h"
 #include "clang/Tooling/Tooling.h"
+#include <map>
 
 namespace clang {
 namespace tidy {
@@ -46,7 +47,8 @@ std::string
 runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
                const Twine &Filename = "input.cc",
                ArrayRef<std::string> ExtraArgs = None,
-               const ClangTidyOptions &ExtraOptions = ClangTidyOptions()) {
+               const ClangTidyOptions &ExtraOptions = ClangTidyOptions(),
+               std::map<StringRef, StringRef> PathsToContent = {}) {
   ClangTidyOptions Options = ExtraOptions;
   Options.Checks = "*";
   ClangTidyContext Context(llvm::make_unique<DefaultOptionsProvider>(
@@ -60,6 +62,7 @@ runCheckOnCode(StringRef Code, std::vect
   std::vector<std::string> ArgCXX11(1, "clang-tidy");
   ArgCXX11.push_back("-fsyntax-only");
   ArgCXX11.push_back("-std=c++11");
+  ArgCXX11.push_back("-Iinclude");
   ArgCXX11.insert(ArgCXX11.end(), ExtraArgs.begin(), ExtraArgs.end());
   ArgCXX11.push_back(Filename.str());
   llvm::IntrusiveRefCntPtr<FileManager> Files(
@@ -67,6 +70,10 @@ runCheckOnCode(StringRef Code, std::vect
   tooling::ToolInvocation Invocation(
       ArgCXX11, new TestClangTidyAction(Check, Finder, Context), Files.get());
   Invocation.mapVirtualFile(Filename.str(), Code);
+  for (const auto & FileContent : PathsToContent) {
+    Invocation.mapVirtualFile(Twine("include/" + FileContent.first).str(),
+                              FileContent.second);
+  }
   Invocation.setDiagnosticConsumer(&DiagConsumer);
   if (!Invocation.run())
     return "";

Added: clang-tools-extra/trunk/unittests/clang-tidy/IncludeInserterTest.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/IncludeInserterTest.cpp?rev=244586&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-tidy/IncludeInserterTest.cpp (added)
+++ clang-tools-extra/trunk/unittests/clang-tidy/IncludeInserterTest.cpp Tue Aug 11 06:37:48 2015
@@ -0,0 +1,407 @@
+//===---- IncludeInserterTest.cpp - clang-tidy ----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../clang-tidy/IncludeInserter.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "ClangTidyTest.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace {
+
+class IncludeInserterCheckBase : public ClangTidyCheck {
+public:
+  using ClangTidyCheck::ClangTidyCheck;
+  void registerPPCallbacks(CompilerInstance &Compiler) override {
+    Inserter.reset(new IncludeInserter(Compiler.getSourceManager(),
+                                       Compiler.getLangOpts(),
+                                       IncludeSorter::IS_Google));
+    Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks());
+  }
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override {
+    Finder->addMatcher(ast_matchers::declStmt().bind("stmt"), this);
+  }
+
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
+    auto Fixit =
+        Inserter->CreateIncludeInsertion(Result.SourceManager->getMainFileID(),
+                                         HeaderToInclude(), IsAngledInclude());
+    if (Fixit) {
+      diag(Result.Nodes.getStmtAs<DeclStmt>("stmt")->getLocStart(), "foo, bar")
+          << *Fixit;
+    }
+    // Second include should yield no Fixit.
+    Fixit =
+        Inserter->CreateIncludeInsertion(Result.SourceManager->getMainFileID(),
+                                         HeaderToInclude(), IsAngledInclude());
+    EXPECT_FALSE(Fixit);
+  }
+
+  virtual StringRef HeaderToInclude() const = 0;
+  virtual bool IsAngledInclude() const = 0;
+
+  std::unique_ptr<IncludeInserter> Inserter;
+};
+
+class NonSystemHeaderInserterCheck : public IncludeInserterCheckBase {
+public:
+  using IncludeInserterCheckBase::IncludeInserterCheckBase;
+  StringRef HeaderToInclude() const override { return "path/to/header.h"; }
+  bool IsAngledInclude() const override { return false; }
+};
+
+class CXXSystemIncludeInserterCheck : public IncludeInserterCheckBase {
+public:
+  using IncludeInserterCheckBase::IncludeInserterCheckBase;
+  StringRef HeaderToInclude() const override { return "set"; }
+  bool IsAngledInclude() const override { return true; }
+};
+
+template <typename Check>
+std::string runCheckOnCode(StringRef Code, StringRef Filename,
+                           size_t NumWarningsExpected) {
+  std::vector<ClangTidyError> Errors;
+  return test::runCheckOnCode<Check>(Code, &Errors, Filename, None,
+                                     ClangTidyOptions(),
+                                     {// Main file include
+                                      {"devtools/cymbal/clang_tidy/tests/"
+                                       "insert_includes_test_header.h",
+                                       "\n"},
+                                      // Non system headers
+                                      {"path/to/a/header.h", "\n"},
+                                      {"path/to/z/header.h", "\n"},
+                                      {"path/to/header.h", "\n"},
+                                      // Fake system headers.
+                                      {"stdlib.h", "\n"},
+                                      {"unistd.h", "\n"},
+                                      {"list", "\n"},
+                                      {"map", "\n"},
+                                      {"set", "\n"},
+                                      {"vector", "\n"}});
+  EXPECT_EQ(NumWarningsExpected, Errors.size());
+}
+
+TEST(IncludeInserterTest, InsertAfterLastNonSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_input2.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertBeforeFirstNonSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_input2.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertBetweenNonSystemIncludes) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_input2.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, NonSystemIncludeAlreadyIncluded) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+#include "path/to/header.h"
+#include "path/to/z/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  EXPECT_EQ(PreCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                         PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                  "insert_includes_test_input2.cc",
+                         0));
+}
+
+TEST(IncludeInserterTest, InsertNonSystemIncludeAfterLastCXXSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertNonSystemIncludeAfterMainFileInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include "path/to/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<NonSystemHeaderInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterLastCXXSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <list>
+#include <map>
+#include <set>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeBeforeFirstCXXSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <set>
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeBetweenCXXSystemIncludes) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <map>
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterMainFileInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <set>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+TEST(IncludeInserterTest, InsertCXXSystemIncludeAfterCSystemInclude) {
+  const char *PreCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <stdlib.h>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+  const char *PostCode = R"(
+#include "devtools/cymbal/clang_tidy/tests/insert_includes_test_header.h"
+
+#include <stdlib.h>
+
+#include <set>
+
+#include "path/to/a/header.h"
+
+void foo() {
+  int a = 0;
+})";
+
+  EXPECT_EQ(PostCode, runCheckOnCode<CXXSystemIncludeInserterCheck>(
+                          PreCode, "devtools/cymbal/clang_tidy/tests/"
+                                   "insert_includes_test_header.cc",
+                          1));
+}
+
+} // namespace
+} // namespace tidy
+} // namespace clang




More information about the cfe-commits mailing list