| 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 | DiagnosticOptions DiagOpts; |
| 100 | DiagnosticsEngine Diagnostics( |
| 101 | IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), DiagOpts); |
| 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 | |