1 | //===-- ClangApplyReplacementsMain.cpp - Main file for the tool -----------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | /// |
9 | /// \file |
10 | /// This file provides the main function for the |
11 | /// clang-apply-replacements tool. |
12 | /// |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "clang-apply-replacements/Tooling/ApplyReplacements.h" |
16 | #include "clang/Basic/Diagnostic.h" |
17 | #include "clang/Basic/DiagnosticOptions.h" |
18 | #include "clang/Basic/SourceManager.h" |
19 | #include "clang/Basic/Version.h" |
20 | #include "clang/Format/Format.h" |
21 | #include "clang/Rewrite/Core/Rewriter.h" |
22 | #include "llvm/ADT/STLExtras.h" |
23 | #include "llvm/ADT/StringSet.h" |
24 | #include "llvm/Support/CommandLine.h" |
25 | |
26 | using namespace llvm; |
27 | using namespace clang; |
28 | using namespace clang::replace; |
29 | |
30 | static cl::opt<std::string> Directory(cl::Positional, cl::Required, |
31 | cl::desc("<Search Root Directory>" )); |
32 | |
33 | static cl::OptionCategory ReplacementCategory("Replacement Options" ); |
34 | static cl::OptionCategory FormattingCategory("Formatting Options" ); |
35 | |
36 | const cl::OptionCategory *VisibleCategories[] = {&ReplacementCategory, |
37 | &FormattingCategory}; |
38 | |
39 | static cl::opt<bool> RemoveTUReplacementFiles( |
40 | "remove-change-desc-files" , |
41 | cl::desc("Remove the change description files regardless of successful\n" |
42 | "merging/replacing." ), |
43 | cl::init(Val: false), cl::cat(ReplacementCategory)); |
44 | |
45 | static cl::opt<bool> IgnoreInsertConflict( |
46 | "ignore-insert-conflict" , |
47 | cl::desc("Ignore insert conflict and keep running to fix." ), |
48 | cl::init(Val: false), cl::cat(ReplacementCategory)); |
49 | |
50 | static 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 | |
61 | static 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(Val: "" ), cl::cat(FormattingCategory)); |
67 | |
68 | static cl::opt<std::string> |
69 | FormatStyleOpt("style" , cl::desc(format::StyleOptionHelpDescription), |
70 | cl::init(Val: "LLVM" ), cl::cat(FormattingCategory)); |
71 | |
72 | namespace { |
73 | // Helper object to remove the TUReplacement and TUDiagnostic (triggered by |
74 | // "remove-change-desc-files" command line option) when exiting current scope. |
75 | class ScopedFileRemover { |
76 | public: |
77 | ScopedFileRemover(const TUReplacementFiles &Files, |
78 | clang::DiagnosticsEngine &Diagnostics) |
79 | : TURFiles(Files), Diag(Diagnostics) {} |
80 | |
81 | ~ScopedFileRemover() { deleteReplacementFiles(Files: TURFiles, Diagnostics&: Diag); } |
82 | |
83 | private: |
84 | const TUReplacementFiles &TURFiles; |
85 | clang::DiagnosticsEngine &Diag; |
86 | }; |
87 | } // namespace |
88 | |
89 | static void printVersion(raw_ostream &OS) { |
90 | OS << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n" ; |
91 | } |
92 | |
93 | int main(int argc, char **argv) { |
94 | cl::HideUnrelatedOptions(Categories: ArrayRef(VisibleCategories)); |
95 | |
96 | cl::SetVersionPrinter(printVersion); |
97 | cl::ParseCommandLineOptions(argc, argv); |
98 | |
99 | IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); |
100 | DiagnosticsEngine Diagnostics( |
101 | IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), DiagOpts.get()); |
102 | |
103 | // Determine a formatting style from options. |
104 | auto FormatStyleOrError = format::getStyle(StyleName: FormatStyleOpt, FileName: FormatStyleConfig, |
105 | FallbackStyle: format::DefaultFallbackStyle); |
106 | if (!FormatStyleOrError) { |
107 | llvm::errs() << llvm::toString(E: FormatStyleOrError.takeError()) << "\n" ; |
108 | return 1; |
109 | } |
110 | format::FormatStyle FormatStyle = std::move(*FormatStyleOrError); |
111 | |
112 | TUReplacements TURs; |
113 | TUReplacementFiles TUFiles; |
114 | |
115 | std::error_code ErrorCode = |
116 | collectReplacementsFromDirectory(Directory, TUs&: TURs, TUFiles, Diagnostics); |
117 | |
118 | TUDiagnostics TUDs; |
119 | TUFiles.clear(); |
120 | ErrorCode = |
121 | collectReplacementsFromDirectory(Directory, TUs&: TUDs, TUFiles, Diagnostics); |
122 | |
123 | if (ErrorCode) { |
124 | errs() << "Trouble iterating over directory '" << Directory |
125 | << "': " << ErrorCode.message() << "\n" ; |
126 | return 1; |
127 | } |
128 | |
129 | // Remove the TUReplacementFiles (triggered by "remove-change-desc-files" |
130 | // command line option) when exiting main(). |
131 | std::unique_ptr<ScopedFileRemover> Remover; |
132 | if (RemoveTUReplacementFiles) |
133 | Remover.reset(p: new ScopedFileRemover(TUFiles, Diagnostics)); |
134 | |
135 | FileManager Files((FileSystemOptions())); |
136 | SourceManager SM(Diagnostics, Files); |
137 | |
138 | FileToChangesMap Changes; |
139 | if (!mergeAndDeduplicate(TUs: TURs, TUDs, FileChanges&: Changes, SM, IgnoreInsertConflict)) |
140 | return 1; |
141 | |
142 | tooling::ApplyChangesSpec Spec; |
143 | Spec.Cleanup = true; |
144 | Spec.Format = DoFormat ? tooling::ApplyChangesSpec::kAll |
145 | : tooling::ApplyChangesSpec::kNone; |
146 | Spec.Style = DoFormat ? FormatStyle : format::getNoStyle(); |
147 | |
148 | for (const auto &FileChange : Changes) { |
149 | FileEntryRef Entry = FileChange.first; |
150 | StringRef FileName = Entry.getName(); |
151 | llvm::Expected<std::string> NewFileData = |
152 | applyChanges(File: FileName, Changes: FileChange.second, Spec, Diagnostics); |
153 | if (!NewFileData) { |
154 | errs() << llvm::toString(E: NewFileData.takeError()) << "\n" ; |
155 | continue; |
156 | } |
157 | |
158 | // Write new file to disk |
159 | std::error_code EC; |
160 | llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::OF_None); |
161 | if (EC) { |
162 | llvm::errs() << "Could not open " << FileName << " for writing\n" ; |
163 | continue; |
164 | } |
165 | FileStream << *NewFileData; |
166 | } |
167 | |
168 | return 0; |
169 | } |
170 | |