blob: 2e5271bb5468f6761be0c7417402486c51e004c8 [file] [log] [blame]
Edwin Vaneb225be22013-09-03 17:58:19 +00001//===-- ClangApplyReplacementsMain.cpp - Main file for the tool -----------===//
Edwin Vane3bf14ce2013-08-22 13:07:14 +00002//
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
Edwin Vaneb225be22013-09-03 17:58:19 +000011/// \brief This file provides the main function for the
12/// clang-apply-replacements tool.
Edwin Vane3bf14ce2013-08-22 13:07:14 +000013///
14//===----------------------------------------------------------------------===//
15
Edwin Vaneb225be22013-09-03 17:58:19 +000016#include "clang-apply-replacements/Tooling/ApplyReplacements.h"
Edwin Vane3bf14ce2013-08-22 13:07:14 +000017#include "clang/Basic/Diagnostic.h"
18#include "clang/Basic/DiagnosticOptions.h"
Edwin Vane1106ef22013-08-22 13:40:32 +000019#include "clang/Basic/SourceManager.h"
Edwin Vane6d5350c2013-09-24 18:14:54 +000020#include "clang/Basic/Version.h"
Edwin Vane59d93af2013-09-30 13:59:21 +000021#include "clang/Format/Format.h"
Edwin Vanef18633c2013-08-28 17:19:10 +000022#include "clang/Rewrite/Core/Rewriter.h"
Edwin Vane6d5350c2013-09-24 18:14:54 +000023#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/StringSet.h"
Edwin Vane3bf14ce2013-08-22 13:07:14 +000025#include "llvm/Support/CommandLine.h"
26
27using namespace llvm;
28using namespace clang;
29using namespace clang::replace;
Rafael Espindola002840c2014-06-12 22:01:48 +000030using std::error_code;
Edwin Vane3bf14ce2013-08-22 13:07:14 +000031
32static cl::opt<std::string> Directory(cl::Positional, cl::Required,
33 cl::desc("<Search Root Directory>"));
34
Edwin Vane59d93af2013-09-30 13:59:21 +000035static cl::OptionCategory FormattingCategory("Formatting Options");
36
Tareq A. Siraj11755522013-08-26 19:58:59 +000037static cl::opt<bool> RemoveTUReplacementFiles(
38 "remove-change-desc-files",
39 cl::desc("Remove the change description files regardless of successful\n"
40 "merging/replacing."),
41 cl::init(false));
42
Edwin Vane6d5350c2013-09-24 18:14:54 +000043// Update this list of options to show in -help as new options are added.
44// Should add even those options marked as 'Hidden'. Any option not listed
45// here will get marked 'ReallyHidden' so they don't appear in any -help text.
Edwin Vane59d93af2013-09-30 13:59:21 +000046const char *OptionsToShow[] = { "help", "version",
47 "remove-change-desc-files", "format",
48 "style-config", "style" };
49
50static cl::opt<bool> DoFormat(
51 "format",
52 cl::desc("Enable formatting of code changed by applying replacements.\n"
53 "Use -style to choose formatting style.\n"),
54 cl::cat(FormattingCategory));
55
56// FIXME: Consider making the default behaviour for finding a style
57// configuration file to start the search anew for every file being changed to
58// handle situations where the style is different for different parts of a
59// project.
60
61static cl::opt<std::string> FormatStyleConfig(
62 "style-config",
63 cl::desc("Path to a directory containing a .clang-format file\n"
64 "describing a formatting style to use for formatting\n"
65 "code when -style=file.\n"),
66 cl::init(""), cl::cat(FormattingCategory));
67
68static cl::opt<std::string>
69FormatStyleOpt("style", cl::desc(format::StyleOptionHelpDescription),
70 cl::init("LLVM"), cl::cat(FormattingCategory));
Edwin Vane6d5350c2013-09-24 18:14:54 +000071
Tareq A. Siraj11755522013-08-26 19:58:59 +000072// Helper object to remove the TUReplacement files (triggered by
73// "remove-change-desc-files" command line option) when exiting current scope.
74class ScopedFileRemover {
75public:
76 ScopedFileRemover(const TUReplacementFiles &Files,
77 clang::DiagnosticsEngine &Diagnostics)
78 : TURFiles(Files), Diag(Diagnostics) {}
79
80 ~ScopedFileRemover() {
81 deleteReplacementFiles(TURFiles, Diag);
82 }
83
84private:
85 const TUReplacementFiles &TURFiles;
86 clang::DiagnosticsEngine &Diag;
87};
88
Edwin Vane6d5350c2013-09-24 18:14:54 +000089void printVersion() {
90 outs() << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n";
91}
92
Edwin Vane59d93af2013-09-30 13:59:21 +000093/// \brief Convenience function to get rewritten content for \c Filename from
94/// \c Rewrites.
95///
96/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath().
97/// \post Replacements.empty() -> Result.empty()
98///
99/// \param[in] Replacements Replacements to apply
100/// \param[in] Rewrites Rewriter to use to apply replacements.
101/// \param[out] Result Contents of the file after applying replacements if
102/// replacements were provided.
103///
104/// \returns \li true if all replacements were applied successfully.
105/// \li false if at least one replacement failed to apply.
106static bool
107getRewrittenData(const std::vector<tooling::Replacement> &Replacements,
108 Rewriter &Rewrites, std::string &Result) {
109 if (Replacements.empty()) return true;
110
111 if (!tooling::applyAllReplacements(Replacements, Rewrites))
112 return false;
113
114 SourceManager &SM = Rewrites.getSourceMgr();
115 FileManager &Files = SM.getFileManager();
116
117 StringRef FileName = Replacements.begin()->getFilePath();
118 const clang::FileEntry *Entry = Files.getFile(FileName);
119 assert(Entry && "Expected an existing file");
120 FileID ID = SM.translateFile(Entry);
121 assert(!ID.isInvalid() && "Expected a valid FileID");
122 const RewriteBuffer *Buffer = Rewrites.getRewriteBufferFor(ID);
123 Result = std::string(Buffer->begin(), Buffer->end());
124
125 return true;
126}
127
128/// \brief Apply \c Replacements and return the new file contents.
129///
130/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath().
131/// \post Replacements.empty() -> Result.empty()
132///
133/// \param[in] Replacements Replacements to apply.
134/// \param[out] Result Contents of the file after applying replacements if
135/// replacements were provided.
136/// \param[in] Diagnostics For diagnostic output.
137///
138/// \returns \li true if all replacements applied successfully.
139/// \li false if at least one replacement failed to apply.
140bool applyReplacements(const std::vector<tooling::Replacement> &Replacements,
141 std::string &Result,
142 DiagnosticsEngine &Diagnostics) {
143 FileManager Files((FileSystemOptions()));
144 SourceManager SM(Diagnostics, Files);
145 Rewriter Rewrites(SM, LangOptions());
146
147 return getRewrittenData(Replacements, Rewrites, Result);
148}
149
150/// \brief Apply code formatting to all places where replacements were made.
151///
152/// \pre !Replacements.empty().
153/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath().
154/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset().
155///
156/// \param[in] Replacements Replacements that were made to the file. Provided
157/// to indicate where changes were made.
158/// \param[in] FileData The contents of the file \b after \c Replacements have
159/// been applied.
160/// \param[out] FormattedFileData The contents of the file after reformatting.
Edwin Vane2fb10d12013-10-01 13:21:39 +0000161/// \param[in] FormatStyle Style to apply.
Edwin Vane59d93af2013-09-30 13:59:21 +0000162/// \param[in] Diagnostics For diagnostic output.
163///
164/// \returns \li true if reformatting replacements were all successfully
165/// applied.
166/// \li false if at least one reformatting replacement failed to apply.
167bool applyFormatting(const std::vector<tooling::Replacement> &Replacements,
168 const StringRef FileData,
169 std::string &FormattedFileData,
170 const format::FormatStyle &FormatStyle,
171 DiagnosticsEngine &Diagnostics) {
172 assert(!Replacements.empty() && "Need at least one replacement");
173
174 RangeVector Ranges = calculateChangedRanges(Replacements);
175
176 StringRef FileName = Replacements.begin()->getFilePath();
177 tooling::Replacements R =
178 format::reformat(FormatStyle, FileData, Ranges, FileName);
179
180 // FIXME: Remove this copy when tooling::Replacements is implemented as a
181 // vector instead of a set.
182 std::vector<tooling::Replacement> FormattingReplacements;
183 std::copy(R.begin(), R.end(), back_inserter(FormattingReplacements));
184
185 if (FormattingReplacements.empty()) {
186 FormattedFileData = FileData;
187 return true;
188 }
189
190 FileManager Files((FileSystemOptions()));
191 SourceManager SM(Diagnostics, Files);
192 SM.overrideFileContents(Files.getFile(FileName),
193 llvm::MemoryBuffer::getMemBufferCopy(FileData));
194 Rewriter Rewrites(SM, LangOptions());
195
196 return getRewrittenData(FormattingReplacements, Rewrites, FormattedFileData);
197}
198
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000199int main(int argc, char **argv) {
Edwin Vane6d5350c2013-09-24 18:14:54 +0000200 // Only include our options in -help output.
201 StringMap<cl::Option*> OptMap;
202 cl::getRegisteredOptions(OptMap);
203 const char **EndOpts = OptionsToShow + array_lengthof(OptionsToShow);
204 for (StringMap<cl::Option *>::iterator I = OptMap.begin(), E = OptMap.end();
205 I != E; ++I) {
206 if (std::find(OptionsToShow, EndOpts, I->getKey()) == EndOpts)
207 I->getValue()->setHiddenFlag(cl::ReallyHidden);
208 }
209
210 cl::SetVersionPrinter(&printVersion);
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000211 cl::ParseCommandLineOptions(argc, argv);
212
213 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
214 DiagnosticsEngine Diagnostics(
215 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
216 DiagOpts.getPtr());
217
Edwin Vane59d93af2013-09-30 13:59:21 +0000218 // Determine a formatting style from options.
219 format::FormatStyle FormatStyle;
220 if (DoFormat)
Alexander Kornienko3daaf322013-12-02 15:22:30 +0000221 FormatStyle = format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM");
Edwin Vane59d93af2013-09-30 13:59:21 +0000222
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000223 TUReplacements TUs;
Tareq A. Siraj11755522013-08-26 19:58:59 +0000224 TUReplacementFiles TURFiles;
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000225
226 error_code ErrorCode =
Tareq A. Siraj11755522013-08-26 19:58:59 +0000227 collectReplacementsFromDirectory(Directory, TUs, TURFiles, Diagnostics);
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000228
229 if (ErrorCode) {
230 errs() << "Trouble iterating over directory '" << Directory
231 << "': " << ErrorCode.message() << "\n";
Edwin Vanec0f00b72013-10-05 12:15:58 +0000232 return 1;
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000233 }
234
Tareq A. Siraj11755522013-08-26 19:58:59 +0000235 // Remove the TUReplacementFiles (triggered by "remove-change-desc-files"
236 // command line option) when exiting main().
Ahmed Charles6a2dc5c2014-03-09 09:24:40 +0000237 std::unique_ptr<ScopedFileRemover> Remover;
Tareq A. Siraj11755522013-08-26 19:58:59 +0000238 if (RemoveTUReplacementFiles)
239 Remover.reset(new ScopedFileRemover(TURFiles, Diagnostics));
240
Edwin Vane1106ef22013-08-22 13:40:32 +0000241 FileManager Files((FileSystemOptions()));
242 SourceManager SM(Diagnostics, Files);
243
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000244 FileToReplacementsMap GroupedReplacements;
Edwin Vane1106ef22013-08-22 13:40:32 +0000245 if (!mergeAndDeduplicate(TUs, GroupedReplacements, SM))
246 return 1;
247
Edwin Vane59d93af2013-09-30 13:59:21 +0000248 Rewriter ReplacementsRewriter(SM, LangOptions());
Edwin Vanef18633c2013-08-28 17:19:10 +0000249
Edwin Vane59d93af2013-09-30 13:59:21 +0000250 for (FileToReplacementsMap::const_iterator I = GroupedReplacements.begin(),
251 E = GroupedReplacements.end();
252 I != E; ++I) {
253
254 std::string NewFileData;
255
256 // This shouldn't happen but if a file somehow has no replacements skip to
257 // next file.
258 if (I->getValue().empty())
259 continue;
260
261 if (!applyReplacements(I->getValue(), NewFileData, Diagnostics)) {
262 errs() << "Failed to apply replacements to " << I->getKey() << "\n";
263 continue;
264 }
265
266 // Apply formatting if requested.
267 if (DoFormat && !applyFormatting(I->getValue(), NewFileData, NewFileData,
268 FormatStyle, Diagnostics)) {
269 errs() << "Failed to apply reformatting replacements for " << I->getKey()
270 << "\n";
271 continue;
272 }
273
274 // Write new file to disk
275 std::string ErrorInfo;
Rafael Espindoladc59a362014-02-24 15:41:44 +0000276 llvm::raw_fd_ostream FileStream(I->getKey().str().c_str(), ErrorInfo,
Rafael Espindola26af2622014-02-24 18:21:04 +0000277 llvm::sys::fs::F_Text);
Edwin Vane59d93af2013-09-30 13:59:21 +0000278 if (!ErrorInfo.empty()) {
279 llvm::errs() << "Could not open " << I->getKey() << " for writing\n";
280 continue;
281 }
282
283 FileStream << NewFileData;
284 }
Edwin Vanef18633c2013-08-28 17:19:10 +0000285
286 return 0;
Edwin Vane3bf14ce2013-08-22 13:07:14 +0000287}