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