clang-tools  3.8.0
IncludeSorter.cpp
Go to the documentation of this file.
1 //===---------- IncludeSorter.cpp - clang-tidy ----------------------------===//
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 #include "IncludeSorter.h"
11 #include "clang/Lex/Lexer.h"
12 
13 namespace clang {
14 namespace tidy {
15 
16 namespace {
17 
18 StringRef RemoveFirstSuffix(StringRef Str, ArrayRef<const char *> Suffixes) {
19  for (StringRef Suffix : Suffixes) {
20  if (Str.endswith(Suffix)) {
21  return Str.substr(0, Str.size() - Suffix.size());
22  }
23  }
24  return Str;
25 }
26 
27 StringRef MakeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) {
28  // The list of suffixes to remove from source file names to get the
29  // "canonical" file names.
30  // E.g. tools/sort_includes.cc and tools/sort_includes_test.cc
31  // would both canonicalize to tools/sort_includes and tools/sort_includes.h
32  // (once canonicalized) will match as being the main include file associated
33  // with the source files.
34  if (Style == IncludeSorter::IS_LLVM) {
35  return RemoveFirstSuffix(
36  RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), {"Test"});
37  }
38  return RemoveFirstSuffix(
39  RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}),
40  {"_unittest", "_regtest", "_test"});
41 }
42 
43 // Scan to the end of the line and return the offset of the next line.
44 size_t FindNextLine(const char *Text) {
45  size_t EOLIndex = std::strcspn(Text, "\n");
46  return Text[EOLIndex] == '\0' ? EOLIndex : EOLIndex + 1;
47 }
48 
50 DetermineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile,
52  // Compute the two "canonical" forms of the include's filename sans extension.
53  // The first form is the include's filename without ".h" or "-inl.h" at the
54  // end. The second form is the first form with "/public/" in the file path
55  // replaced by "/internal/".
56  if (IsAngled) {
57  // If the system include (<foo>) ends with ".h", then it is a normal C-style
58  // include. Otherwise assume it is a C++-style extensionless include.
59  return IncludeFile.endswith(".h") ? IncludeSorter::IK_CSystemInclude
61  }
62  StringRef CanonicalInclude = MakeCanonicalName(IncludeFile, Style);
63  if (CanonicalFile.equals(CanonicalInclude)) {
65  }
66  if (Style == IncludeSorter::IS_Google) {
67  std::pair<StringRef, StringRef> Parts = CanonicalInclude.split("/public/");
68  std::string AltCanonicalInclude =
69  Parts.first.str() + "/internal/" + Parts.second.str();
70  std::string ProtoCanonicalInclude =
71  Parts.first.str() + "/proto/" + Parts.second.str();
72 
73  // Determine the kind of this inclusion.
74  if (CanonicalFile.equals(AltCanonicalInclude) ||
75  CanonicalFile.equals(ProtoCanonicalInclude)) {
77  }
78  }
80 }
81 
82 } // namespace
83 
85  const LangOptions *LangOpts, const FileID FileID,
86  StringRef FileName, IncludeStyle Style)
87  : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style),
88  CurrentFileID(FileID), CanonicalFile(MakeCanonicalName(FileName, Style)) {
89 }
90 
91 void IncludeSorter::AddInclude(StringRef FileName, bool IsAngled,
92  SourceLocation HashLocation,
93  SourceLocation EndLocation) {
94  int Offset = FindNextLine(SourceMgr->getCharacterData(EndLocation));
95 
96  // Record the relevant location information for this inclusion directive.
97  IncludeLocations[FileName].push_back(
98  SourceRange(HashLocation, EndLocation.getLocWithOffset(Offset)));
99  SourceLocations.push_back(IncludeLocations[FileName].back());
100 
101  // Stop if this inclusion is a duplicate.
102  if (IncludeLocations[FileName].size() > 1)
103  return;
104 
105  // Add the included file's name to the appropriate bucket.
106  IncludeKinds Kind =
107  DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style);
108  if (Kind != IK_InvalidInclude)
109  IncludeBucket[Kind].push_back(FileName.str());
110 }
111 
112 Optional<FixItHint> IncludeSorter::CreateIncludeInsertion(StringRef FileName,
113  bool IsAngled) {
114  std::string IncludeStmt =
115  IsAngled ? llvm::Twine("#include <" + FileName + ">\n").str()
116  : llvm::Twine("#include \"" + FileName + "\"\n").str();
117  if (SourceLocations.empty()) {
118  // If there are no includes in this file, add it in the first line.
119  // FIXME: insert after the file comment or the header guard, if present.
120  IncludeStmt.append("\n");
121  return FixItHint::CreateInsertion(
122  SourceMgr->getLocForStartOfFile(CurrentFileID), IncludeStmt);
123  }
124 
125  auto IncludeKind =
126  DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style);
127 
128  if (!IncludeBucket[IncludeKind].empty()) {
129  for (const std::string &IncludeEntry : IncludeBucket[IncludeKind]) {
130  if (FileName < IncludeEntry) {
131  const auto &Location = IncludeLocations[IncludeEntry][0];
132  return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt);
133  } else if (FileName == IncludeEntry) {
134  return llvm::None;
135  }
136  }
137  // FileName comes after all include entries in bucket, insert it after
138  // last.
139  const std::string &LastInclude = IncludeBucket[IncludeKind].back();
140  SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
141  return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(),
142  IncludeStmt);
143  }
144  // Find the non-empty include bucket to be sorted directly above
145  // 'IncludeKind'. If such a bucket exists, we'll want to sort the include
146  // after that bucket. If no such bucket exists, find the first non-empty
147  // include bucket in the file. In that case, we'll want to sort the include
148  // before that bucket.
149  IncludeKinds NonEmptyKind = IK_InvalidInclude;
150  for (int i = IK_InvalidInclude - 1; i >= 0; --i) {
151  if (!IncludeBucket[i].empty()) {
152  NonEmptyKind = static_cast<IncludeKinds>(i);
153  if (NonEmptyKind < IncludeKind)
154  break;
155  }
156  }
157  if (NonEmptyKind == IK_InvalidInclude) {
158  return llvm::None;
159  }
160 
161  if (NonEmptyKind < IncludeKind) {
162  // Create a block after.
163  const std::string &LastInclude = IncludeBucket[NonEmptyKind].back();
164  SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back();
165  IncludeStmt = '\n' + IncludeStmt;
166  return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(),
167  IncludeStmt);
168  }
169  // Create a block before.
170  const std::string &FirstInclude = IncludeBucket[NonEmptyKind][0];
171  SourceRange FirstIncludeLocation = IncludeLocations[FirstInclude].back();
172  IncludeStmt.append("\n");
173  return FixItHint::CreateInsertion(FirstIncludeLocation.getBegin(),
174  IncludeStmt);
175 }
176 
177 std::vector<FixItHint> IncludeSorter::GetEdits() {
178  if (SourceLocations.empty())
179  return {};
180 
181  typedef std::map<int, std::pair<SourceRange, std::string>>
182  FileLineToSourceEditMap;
183  FileLineToSourceEditMap Edits;
184  auto SourceLocationIterator = SourceLocations.begin();
185  auto SourceLocationIteratorEnd = SourceLocations.end();
186 
187  // Compute the Edits that need to be done to each line to add, replace, or
188  // delete inclusions.
189  for (int IncludeKind = 0; IncludeKind < IK_InvalidInclude; ++IncludeKind) {
190  std::sort(IncludeBucket[IncludeKind].begin(),
191  IncludeBucket[IncludeKind].end());
192  for (const auto &IncludeEntry : IncludeBucket[IncludeKind]) {
193  auto &Location = IncludeLocations[IncludeEntry];
194  SourceRangeVector::iterator LocationIterator = Location.begin();
195  SourceRangeVector::iterator LocationIteratorEnd = Location.end();
196  SourceRange FirstLocation = *LocationIterator;
197 
198  // If the first occurrence of a particular include is on the current
199  // source line we are examining, leave it alone.
200  if (FirstLocation == *SourceLocationIterator)
201  ++LocationIterator;
202 
203  // Add the deletion Edits for any (remaining) instances of this inclusion,
204  // and remove their Locations from the source Locations to be processed.
205  for (; LocationIterator != LocationIteratorEnd; ++LocationIterator) {
206  int LineNumber =
207  SourceMgr->getSpellingLineNumber(LocationIterator->getBegin());
208  Edits[LineNumber] = std::make_pair(*LocationIterator, "");
209  SourceLocationIteratorEnd =
210  std::remove(SourceLocationIterator, SourceLocationIteratorEnd,
211  *LocationIterator);
212  }
213 
214  if (FirstLocation == *SourceLocationIterator) {
215  // Do nothing except move to the next source Location (Location of an
216  // inclusion in the original, unchanged source file).
217  ++SourceLocationIterator;
218  continue;
219  }
220 
221  // Add (or append to) the replacement text for this line in source file.
222  int LineNumber =
223  SourceMgr->getSpellingLineNumber(SourceLocationIterator->getBegin());
224  if (Edits.find(LineNumber) == Edits.end()) {
225  Edits[LineNumber].first =
226  SourceRange(SourceLocationIterator->getBegin());
227  }
228  StringRef SourceText = Lexer::getSourceText(
229  CharSourceRange::getCharRange(FirstLocation), *SourceMgr, *LangOpts);
230  Edits[LineNumber].second.append(SourceText.data(), SourceText.size());
231  }
232 
233  // Clear the bucket.
234  IncludeBucket[IncludeKind].clear();
235  }
236 
237  // Go through the single-line Edits and combine them into blocks of Edits.
238  int CurrentEndLine = 0;
239  SourceRange CurrentRange;
240  std::string CurrentText;
241  std::vector<FixItHint> Fixes;
242  for (const auto &LineEdit : Edits) {
243  // If the current edit is on the next line after the previous edit, add it
244  // to the current block edit.
245  if (LineEdit.first == CurrentEndLine + 1 &&
246  CurrentRange.getBegin() != CurrentRange.getEnd()) {
247  SourceRange EditRange = LineEdit.second.first;
248  if (EditRange.getBegin() != EditRange.getEnd()) {
249  ++CurrentEndLine;
250  CurrentRange.setEnd(EditRange.getEnd());
251  }
252  CurrentText += LineEdit.second.second;
253  // Otherwise report the current block edit and start a new block.
254  } else {
255  if (CurrentEndLine) {
256  Fixes.push_back(CreateFixIt(CurrentRange, CurrentText));
257  }
258 
259  CurrentEndLine = LineEdit.first;
260  CurrentRange = LineEdit.second.first;
261  CurrentText = LineEdit.second.second;
262  }
263  }
264  // Finally, report the current block edit if there is one.
265  if (CurrentEndLine) {
266  Fixes.push_back(CreateFixIt(CurrentRange, CurrentText));
267  }
268 
269  // Reset the remaining internal state.
270  SourceLocations.clear();
271  IncludeLocations.clear();
272  return Fixes;
273 }
274 
275 // Creates a fix-it for the given replacements.
276 // Takes the the source location that will be replaced, and the new text.
277 FixItHint IncludeSorter::CreateFixIt(SourceRange EditRange,
278  const std::string &NewText) {
279  FixItHint Fix;
280  Fix.RemoveRange = CharSourceRange::getCharRange(EditRange);
281  Fix.CodeToInsert = NewText;
282  return Fix;
283 }
284 
286 IncludeSorter::parseIncludeStyle(const std::string &Value) {
287  return Value == "llvm" ? IS_LLVM : IS_Google;
288 }
289 
291  return Style == IS_LLVM ? "llvm" : "google";
292 }
293 
294 } // namespace tidy
295 } // namespace clang
LangOptions LangOpts
Definition: ClangTidy.cpp:168
Optional< FixItHint > CreateIncludeInsertion(StringRef FileName, bool IsAngled)
static IncludeStyle parseIncludeStyle(const std::string &Value)
void AddInclude(StringRef FileName, bool IsAngled, SourceLocation HashLocation, SourceLocation EndLocation)
IncludeSorter(const SourceManager *SourceMgr, const LangOptions *LangOpts, const FileID FileID, StringRef FileName, IncludeStyle Style)
static StringRef toString(IncludeStyle Style)
std::vector< FixItHint > GetEdits()
SourceManager SourceMgr
Definition: ClangTidy.cpp:172
bool IsAngled
true if this was an include with angle brackets
static cl::opt< bool > Fix("fix", cl::desc("Apply suggested fixes. Without -fix-errors\n""clang-tidy will bail out if any compilation\n""errors were found."), cl::init(false), cl::cat(ClangTidyCategory))