clang-tools 3.8.0

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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
49 IncludeSorter::IncludeKinds
50 DetermineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile,
51 bool IsAngled, IncludeSorter::IncludeStyle Style) {
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
60 : IncludeSorter::IK_CXXSystemInclude;
61 }
62 StringRef CanonicalInclude = MakeCanonicalName(IncludeFile, Style);
63 if (CanonicalFile.equals(CanonicalInclude)) {
64 return IncludeSorter::IK_MainTUInclude;
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)) {
76 return IncludeSorter::IK_MainTUInclude;
77 }
78 }
79 return IncludeSorter::IK_NonSystemInclude;
80 }
81
82 } // namespace
83
84 IncludeSorter::IncludeSorter(const SourceManager *SourceMgr,
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
285 IncludeSorter::IncludeStyle
286 IncludeSorter::parseIncludeStyle(const std::string &Value) {
287 return Value == "llvm" ? IS_LLVM : IS_Google;
288 }
289
290 StringRef IncludeSorter::toString(IncludeStyle Style) {
291 return Style == IS_LLVM ? "llvm" : "google";
292 }
293
294 } // namespace tidy
295 } // namespace clang
LangOpts
LangOptions LangOpts
Definition: ClangTidy.cpp:168
clang::tidy::IncludeSorter::CreateIncludeInsertion
Optional< FixItHint > CreateIncludeInsertion(StringRef FileName, bool IsAngled)
Definition: IncludeSorter.cpp:112
clang::tidy::IncludeSorter::IncludeStyle
IncludeStyle
Definition: IncludeSorter.h:26
clang::tidy::IncludeSorter::parseIncludeStyle
static IncludeStyle parseIncludeStyle(const std::string &Value)
Definition: IncludeSorter.cpp:286
clang::tidy::IncludeSorter::IK_CXXSystemInclude
Definition: IncludeSorter.h:38
clang::tidy::IncludeSorter::AddInclude
void AddInclude(StringRef FileName, bool IsAngled, SourceLocation HashLocation, SourceLocation EndLocation)
Definition: IncludeSorter.cpp:91
clang::tidy::IncludeSorter::IncludeSorter
IncludeSorter(const SourceManager *SourceMgr, const LangOptions *LangOpts, const FileID FileID, StringRef FileName, IncludeStyle Style)
Definition: IncludeSorter.cpp:84
clang::tidy::IncludeSorter::toString
static StringRef toString(IncludeStyle Style)
Definition: IncludeSorter.cpp:290
clang::tidy::IncludeSorter::IS_Google
Definition: IncludeSorter.h:26
clang::tidy::IncludeSorter::GetEdits
std::vector< FixItHint > GetEdits()
Definition: IncludeSorter.cpp:177
SourceMgr
SourceManager SourceMgr
Definition: ClangTidy.cpp:172
clang::tidy::IncludeSorter::IK_InvalidInclude
Definition: IncludeSorter.h:40
clang::tidy::IncludeSorter::IK_CSystemInclude
Definition: IncludeSorter.h:37
clang::tidy::IncludeSorter::IK_MainTUInclude
Definition: IncludeSorter.h:36
IsAngled
bool IsAngled
true if this was an include with angle brackets
Definition: IncludeOrderCheck.cpp:37
IncludeSorter.h
clang::tidy::IncludeSorter::IncludeKinds
IncludeKinds
Definition: IncludeSorter.h:35
clang::tidy::IncludeSorter::IS_LLVM
Definition: IncludeSorter.h:26
Fix
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))
clang::tidy::IncludeSorter::IK_NonSystemInclude
Definition: IncludeSorter.h:39
Location
Definition: Modularize.cpp:386