474 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			474 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- ClangIncludeFixer.cpp - Standalone include fixer ------------------===//
 | |
| //
 | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | |
| // See https://llvm.org/LICENSE.txt for license information.
 | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "FuzzySymbolIndex.h"
 | |
| #include "InMemorySymbolIndex.h"
 | |
| #include "IncludeFixer.h"
 | |
| #include "IncludeFixerContext.h"
 | |
| #include "SymbolIndexManager.h"
 | |
| #include "YamlSymbolIndex.h"
 | |
| #include "clang/Format/Format.h"
 | |
| #include "clang/Frontend/TextDiagnosticPrinter.h"
 | |
| #include "clang/Rewrite/Core/Rewriter.h"
 | |
| #include "clang/Tooling/CommonOptionsParser.h"
 | |
| #include "clang/Tooling/Core/Replacement.h"
 | |
| #include "clang/Tooling/Tooling.h"
 | |
| #include "llvm/Support/CommandLine.h"
 | |
| #include "llvm/Support/Path.h"
 | |
| #include "llvm/Support/YAMLTraits.h"
 | |
| 
 | |
| using namespace clang;
 | |
| using namespace llvm;
 | |
| using clang::include_fixer::IncludeFixerContext;
 | |
| 
 | |
| LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(IncludeFixerContext)
 | |
| LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::HeaderInfo)
 | |
| LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(IncludeFixerContext::QuerySymbolInfo)
 | |
| 
 | |
| namespace llvm {
 | |
| namespace yaml {
 | |
| 
 | |
| template <> struct MappingTraits<tooling::Range> {
 | |
|   struct NormalizedRange {
 | |
|     NormalizedRange(const IO &) : Offset(0), Length(0) {}
 | |
| 
 | |
|     NormalizedRange(const IO &, const tooling::Range &R)
 | |
|         : Offset(R.getOffset()), Length(R.getLength()) {}
 | |
| 
 | |
|     tooling::Range denormalize(const IO &) {
 | |
|       return tooling::Range(Offset, Length);
 | |
|     }
 | |
| 
 | |
|     unsigned Offset;
 | |
|     unsigned Length;
 | |
|   };
 | |
|   static void mapping(IO &IO, tooling::Range &Info) {
 | |
|     MappingNormalization<NormalizedRange, tooling::Range> Keys(IO, Info);
 | |
|     IO.mapRequired("Offset", Keys->Offset);
 | |
|     IO.mapRequired("Length", Keys->Length);
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <> struct MappingTraits<IncludeFixerContext::HeaderInfo> {
 | |
|   static void mapping(IO &io, IncludeFixerContext::HeaderInfo &Info) {
 | |
|     io.mapRequired("Header", Info.Header);
 | |
|     io.mapRequired("QualifiedName", Info.QualifiedName);
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <> struct MappingTraits<IncludeFixerContext::QuerySymbolInfo> {
 | |
|   static void mapping(IO &io, IncludeFixerContext::QuerySymbolInfo &Info) {
 | |
|     io.mapRequired("RawIdentifier", Info.RawIdentifier);
 | |
|     io.mapRequired("Range", Info.Range);
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <> struct MappingTraits<IncludeFixerContext> {
 | |
|   static void mapping(IO &IO, IncludeFixerContext &Context) {
 | |
|     IO.mapRequired("QuerySymbolInfos", Context.QuerySymbolInfos);
 | |
|     IO.mapRequired("HeaderInfos", Context.HeaderInfos);
 | |
|     IO.mapRequired("FilePath", Context.FilePath);
 | |
|   }
 | |
| };
 | |
| } // namespace yaml
 | |
| } // namespace llvm
 | |
| 
 | |
| namespace {
 | |
| cl::OptionCategory IncludeFixerCategory("Tool options");
 | |
| 
 | |
| enum DatabaseFormatTy {
 | |
|   fixed,     ///< Hard-coded mapping.
 | |
|   yaml,      ///< Yaml database created by find-all-symbols.
 | |
|   fuzzyYaml, ///< Yaml database with fuzzy-matched identifiers.
 | |
| };
 | |
| 
 | |
| cl::opt<DatabaseFormatTy> DatabaseFormat(
 | |
|     "db", cl::desc("Specify input format"),
 | |
|     cl::values(clEnumVal(fixed, "Hard-coded mapping"),
 | |
|                clEnumVal(yaml, "Yaml database created by find-all-symbols"),
 | |
|                clEnumVal(fuzzyYaml, "Yaml database, with fuzzy-matched names")),
 | |
|     cl::init(yaml), cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<std::string> Input("input",
 | |
|                            cl::desc("String to initialize the database"),
 | |
|                            cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<std::string>
 | |
|     QuerySymbol("query-symbol",
 | |
|                  cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n"
 | |
|                           "database directly without parsing the file."),
 | |
|                  cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<bool>
 | |
|     MinimizeIncludePaths("minimize-paths",
 | |
|                          cl::desc("Whether to minimize added include paths"),
 | |
|                          cl::init(true), cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<bool> Quiet("q", cl::desc("Reduce terminal output"), cl::init(false),
 | |
|                     cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<bool>
 | |
|     STDINMode("stdin",
 | |
|               cl::desc("Override source file's content (in the overlaying\n"
 | |
|                        "virtual file system) with input from <stdin> and run\n"
 | |
|                        "the tool on the new content with the compilation\n"
 | |
|                        "options of the source file. This mode is currently\n"
 | |
|                        "used for editor integration."),
 | |
|               cl::init(false), cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<bool> OutputHeaders(
 | |
|     "output-headers",
 | |
|     cl::desc("Print the symbol being queried and all its relevant headers in\n"
 | |
|              "JSON format to stdout:\n"
 | |
|              "  {\n"
 | |
|              "    \"FilePath\": \"/path/to/foo.cc\",\n"
 | |
|              "    \"QuerySymbolInfos\": [\n"
 | |
|              "       {\"RawIdentifier\": \"foo\",\n"
 | |
|              "        \"Range\": {\"Offset\": 0, \"Length\": 3}}\n"
 | |
|              "    ],\n"
 | |
|              "    \"HeaderInfos\": [ {\"Header\": \"\\\"foo_a.h\\\"\",\n"
 | |
|              "                      \"QualifiedName\": \"a::foo\"} ]\n"
 | |
|              "  }"),
 | |
|     cl::init(false), cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<std::string> InsertHeader(
 | |
|     "insert-header",
 | |
|     cl::desc("Insert a specific header. This should run with STDIN mode.\n"
 | |
|              "The result is written to stdout. It is currently used for\n"
 | |
|              "editor integration. Support YAML/JSON format:\n"
 | |
|              "  -insert-header=\"{\n"
 | |
|              "     FilePath: \"/path/to/foo.cc\",\n"
 | |
|              "     QuerySymbolInfos: [\n"
 | |
|              "       {RawIdentifier: foo,\n"
 | |
|              "        Range: {Offset: 0, Length: 3}}\n"
 | |
|              "     ],\n"
 | |
|              "     HeaderInfos: [ {Headers: \"\\\"foo_a.h\\\"\",\n"
 | |
|              "                     QualifiedName: \"a::foo\"} ]}\""),
 | |
|     cl::init(""), cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| cl::opt<std::string>
 | |
|     Style("style",
 | |
|           cl::desc("Fallback style for reformatting after inserting new\n"
 | |
|                    "headers if there is no clang-format config file found."),
 | |
|           cl::init("llvm"), cl::cat(IncludeFixerCategory));
 | |
| 
 | |
| std::unique_ptr<include_fixer::SymbolIndexManager>
 | |
| createSymbolIndexManager(StringRef FilePath) {
 | |
|   using find_all_symbols::SymbolInfo;
 | |
| 
 | |
|   auto SymbolIndexMgr = std::make_unique<include_fixer::SymbolIndexManager>();
 | |
|   switch (DatabaseFormat) {
 | |
|   case fixed: {
 | |
|     // Parse input and fill the database with it.
 | |
|     // <symbol>=<header><, header...>
 | |
|     // Multiple symbols can be given, separated by semicolons.
 | |
|     std::map<std::string, std::vector<std::string>> SymbolsMap;
 | |
|     SmallVector<StringRef, 4> SemicolonSplits;
 | |
|     StringRef(Input).split(SemicolonSplits, ";");
 | |
|     std::vector<find_all_symbols::SymbolAndSignals> Symbols;
 | |
|     for (StringRef Pair : SemicolonSplits) {
 | |
|       auto Split = Pair.split('=');
 | |
|       std::vector<std::string> Headers;
 | |
|       SmallVector<StringRef, 4> CommaSplits;
 | |
|       Split.second.split(CommaSplits, ",");
 | |
|       for (size_t I = 0, E = CommaSplits.size(); I != E; ++I)
 | |
|         Symbols.push_back(
 | |
|             {SymbolInfo(Split.first.trim(), SymbolInfo::SymbolKind::Unknown,
 | |
|                         CommaSplits[I].trim(), {}),
 | |
|              // Use fake "seen" signal for tests, so first header wins.
 | |
|              SymbolInfo::Signals(/*Seen=*/static_cast<unsigned>(E - I),
 | |
|                                  /*Used=*/0)});
 | |
|     }
 | |
|     SymbolIndexMgr->addSymbolIndex([=]() {
 | |
|       return std::make_unique<include_fixer::InMemorySymbolIndex>(Symbols);
 | |
|     });
 | |
|     break;
 | |
|   }
 | |
|   case yaml: {
 | |
|     auto CreateYamlIdx = [=]() -> std::unique_ptr<include_fixer::SymbolIndex> {
 | |
|       llvm::ErrorOr<std::unique_ptr<include_fixer::YamlSymbolIndex>> DB(
 | |
|           nullptr);
 | |
|       if (!Input.empty()) {
 | |
|         DB = include_fixer::YamlSymbolIndex::createFromFile(Input);
 | |
|       } else {
 | |
|         // If we don't have any input file, look in the directory of the
 | |
|         // first
 | |
|         // file and its parents.
 | |
|         SmallString<128> AbsolutePath(tooling::getAbsolutePath(FilePath));
 | |
|         StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
 | |
|         DB = include_fixer::YamlSymbolIndex::createFromDirectory(
 | |
|             Directory, "find_all_symbols_db.yaml");
 | |
|       }
 | |
| 
 | |
|       if (!DB) {
 | |
|         llvm::errs() << "Couldn't find YAML db: " << DB.getError().message()
 | |
|                      << '\n';
 | |
|         return nullptr;
 | |
|       }
 | |
|       return std::move(*DB);
 | |
|     };
 | |
| 
 | |
|     SymbolIndexMgr->addSymbolIndex(std::move(CreateYamlIdx));
 | |
|     break;
 | |
|   }
 | |
|   case fuzzyYaml: {
 | |
|     // This mode is not very useful, because we don't correct the identifier.
 | |
|     // It's main purpose is to expose FuzzySymbolIndex to tests.
 | |
|     SymbolIndexMgr->addSymbolIndex(
 | |
|         []() -> std::unique_ptr<include_fixer::SymbolIndex> {
 | |
|           auto DB = include_fixer::FuzzySymbolIndex::createFromYAML(Input);
 | |
|           if (!DB) {
 | |
|             llvm::errs() << "Couldn't load fuzzy YAML db: "
 | |
|                          << llvm::toString(DB.takeError()) << '\n';
 | |
|             return nullptr;
 | |
|           }
 | |
|           return std::move(*DB);
 | |
|         });
 | |
|     break;
 | |
|   }
 | |
|   }
 | |
|   return SymbolIndexMgr;
 | |
| }
 | |
| 
 | |
| void writeToJson(llvm::raw_ostream &OS, const IncludeFixerContext& Context) {
 | |
|   OS << "{\n"
 | |
|      << "  \"FilePath\": \""
 | |
|      << llvm::yaml::escape(Context.getFilePath()) << "\",\n"
 | |
|      << "  \"QuerySymbolInfos\": [\n";
 | |
|   for (const auto &Info : Context.getQuerySymbolInfos()) {
 | |
|     OS << "     {\"RawIdentifier\": \"" << Info.RawIdentifier << "\",\n";
 | |
|     OS << "      \"Range\":{";
 | |
|     OS << "\"Offset\":" << Info.Range.getOffset() << ",";
 | |
|     OS << "\"Length\":" << Info.Range.getLength() << "}}";
 | |
|     if (&Info != &Context.getQuerySymbolInfos().back())
 | |
|       OS << ",\n";
 | |
|   }
 | |
|   OS << "\n  ],\n";
 | |
|   OS << "  \"HeaderInfos\": [\n";
 | |
|   const auto &HeaderInfos = Context.getHeaderInfos();
 | |
|   for (const auto &Info : HeaderInfos) {
 | |
|     OS << "     {\"Header\": \"" << llvm::yaml::escape(Info.Header) << "\",\n"
 | |
|        << "      \"QualifiedName\": \"" << Info.QualifiedName << "\"}";
 | |
|     if (&Info != &HeaderInfos.back())
 | |
|       OS << ",\n";
 | |
|   }
 | |
|   OS << "\n";
 | |
|   OS << "  ]\n";
 | |
|   OS << "}\n";
 | |
| }
 | |
| 
 | |
| int includeFixerMain(int argc, const char **argv) {
 | |
|   tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory);
 | |
|   tooling::ClangTool tool(options.getCompilations(),
 | |
|                           options.getSourcePathList());
 | |
| 
 | |
|   llvm::StringRef SourceFilePath = options.getSourcePathList().front();
 | |
|   // In STDINMode, we override the file content with the <stdin> input.
 | |
|   // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of
 | |
|   // the if-block so that `Code` is not released after the if-block.
 | |
|   std::unique_ptr<llvm::MemoryBuffer> Code;
 | |
|   if (STDINMode) {
 | |
|     assert(options.getSourcePathList().size() == 1 &&
 | |
|            "Expect exactly one file path in STDINMode.");
 | |
|     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> CodeOrErr =
 | |
|         MemoryBuffer::getSTDIN();
 | |
|     if (std::error_code EC = CodeOrErr.getError()) {
 | |
|       errs() << EC.message() << "\n";
 | |
|       return 1;
 | |
|     }
 | |
|     Code = std::move(CodeOrErr.get());
 | |
|     if (Code->getBufferSize() == 0)
 | |
|       return 0;  // Skip empty files.
 | |
| 
 | |
|     tool.mapVirtualFile(SourceFilePath, Code->getBuffer());
 | |
|   }
 | |
| 
 | |
|   if (!InsertHeader.empty()) {
 | |
|     if (!STDINMode) {
 | |
|       errs() << "Should be running in STDIN mode\n";
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     llvm::yaml::Input yin(InsertHeader);
 | |
|     IncludeFixerContext Context;
 | |
|     yin >> Context;
 | |
| 
 | |
|     const auto &HeaderInfos = Context.getHeaderInfos();
 | |
|     assert(!HeaderInfos.empty());
 | |
|     // We only accept one unique header.
 | |
|     // Check all elements in HeaderInfos have the same header.
 | |
|     bool IsUniqueHeader = std::equal(
 | |
|         HeaderInfos.begin()+1, HeaderInfos.end(), HeaderInfos.begin(),
 | |
|         [](const IncludeFixerContext::HeaderInfo &LHS,
 | |
|            const IncludeFixerContext::HeaderInfo &RHS) {
 | |
|           return LHS.Header == RHS.Header;
 | |
|         });
 | |
|     if (!IsUniqueHeader) {
 | |
|       errs() << "Expect exactly one unique header.\n";
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     // If a header has multiple symbols, we won't add the missing namespace
 | |
|     // qualifiers because we don't know which one is exactly used.
 | |
|     //
 | |
|     // Check whether all elements in HeaderInfos have the same qualified name.
 | |
|     bool IsUniqueQualifiedName = std::equal(
 | |
|         HeaderInfos.begin() + 1, HeaderInfos.end(), HeaderInfos.begin(),
 | |
|         [](const IncludeFixerContext::HeaderInfo &LHS,
 | |
|            const IncludeFixerContext::HeaderInfo &RHS) {
 | |
|           return LHS.QualifiedName == RHS.QualifiedName;
 | |
|         });
 | |
|     auto InsertStyle = format::getStyle(format::DefaultFormatStyle,
 | |
|                                         Context.getFilePath(), Style);
 | |
|     if (!InsertStyle) {
 | |
|       llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";
 | |
|       return 1;
 | |
|     }
 | |
|     auto Replacements = clang::include_fixer::createIncludeFixerReplacements(
 | |
|         Code->getBuffer(), Context, *InsertStyle,
 | |
|         /*AddQualifiers=*/IsUniqueQualifiedName);
 | |
|     if (!Replacements) {
 | |
|       errs() << "Failed to create replacements: "
 | |
|              << llvm::toString(Replacements.takeError()) << "\n";
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     auto ChangedCode =
 | |
|         tooling::applyAllReplacements(Code->getBuffer(), *Replacements);
 | |
|     if (!ChangedCode) {
 | |
|       llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
 | |
|       return 1;
 | |
|     }
 | |
|     llvm::outs() << *ChangedCode;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   // Set up data source.
 | |
|   std::unique_ptr<include_fixer::SymbolIndexManager> SymbolIndexMgr =
 | |
|       createSymbolIndexManager(SourceFilePath);
 | |
|   if (!SymbolIndexMgr)
 | |
|     return 1;
 | |
| 
 | |
|   // Query symbol mode.
 | |
|   if (!QuerySymbol.empty()) {
 | |
|     auto MatchedSymbols = SymbolIndexMgr->search(
 | |
|         QuerySymbol, /*IsNestedSearch=*/true, SourceFilePath);
 | |
|     for (auto &Symbol : MatchedSymbols) {
 | |
|       std::string HeaderPath = Symbol.getFilePath().str();
 | |
|       Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<')
 | |
|                               ? HeaderPath
 | |
|                               : "\"" + HeaderPath + "\""));
 | |
|     }
 | |
| 
 | |
|     // We leave an empty symbol range as we don't know the range of the symbol
 | |
|     // being queried in this mode. clang-include-fixer won't add namespace
 | |
|     // qualifiers if the symbol range is empty, which also fits this case.
 | |
|     IncludeFixerContext::QuerySymbolInfo Symbol;
 | |
|     Symbol.RawIdentifier = QuerySymbol;
 | |
|     auto Context =
 | |
|         IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols);
 | |
|     writeToJson(llvm::outs(), Context);
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   // Now run our tool.
 | |
|   std::vector<include_fixer::IncludeFixerContext> Contexts;
 | |
|   include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts,
 | |
|                                                    Style, MinimizeIncludePaths);
 | |
| 
 | |
|   if (tool.run(&Factory) != 0) {
 | |
|     // We suppress all Clang diagnostics (because they would be wrong,
 | |
|     // clang-include-fixer does custom recovery) but still want to give some
 | |
|     // feedback in case there was a compiler error we couldn't recover from.
 | |
|     // The most common case for this is a #include in the file that couldn't be
 | |
|     // found.
 | |
|     llvm::errs() << "Fatal compiler error occurred while parsing file!"
 | |
|                     " (incorrect include paths?)\n";
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   assert(!Contexts.empty());
 | |
| 
 | |
|   if (OutputHeaders) {
 | |
|     // FIXME: Print contexts of all processing files instead of the first one.
 | |
|     writeToJson(llvm::outs(), Contexts.front());
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   std::vector<tooling::Replacements> FixerReplacements;
 | |
|   for (const auto &Context : Contexts) {
 | |
|     StringRef FilePath = Context.getFilePath();
 | |
|     auto InsertStyle =
 | |
|         format::getStyle(format::DefaultFormatStyle, FilePath, Style);
 | |
|     if (!InsertStyle) {
 | |
|       llvm::errs() << llvm::toString(InsertStyle.takeError()) << "\n";
 | |
|       return 1;
 | |
|     }
 | |
|     auto Buffer = llvm::MemoryBuffer::getFile(FilePath);
 | |
|     if (!Buffer) {
 | |
|       errs() << "Couldn't open file: " + FilePath.str() + ": "
 | |
|              << Buffer.getError().message() + "\n";
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     auto Replacements = clang::include_fixer::createIncludeFixerReplacements(
 | |
|         Buffer.get()->getBuffer(), Context, *InsertStyle);
 | |
|     if (!Replacements) {
 | |
|       errs() << "Failed to create replacement: "
 | |
|              << llvm::toString(Replacements.takeError()) << "\n";
 | |
|       return 1;
 | |
|     }
 | |
|     FixerReplacements.push_back(*Replacements);
 | |
|   }
 | |
| 
 | |
|   if (!Quiet) {
 | |
|     for (const auto &Context : Contexts) {
 | |
|       if (!Context.getHeaderInfos().empty()) {
 | |
|         llvm::errs() << "Added #include "
 | |
|                      << Context.getHeaderInfos().front().Header << " for "
 | |
|                      << Context.getFilePath() << "\n";
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (STDINMode) {
 | |
|     assert(FixerReplacements.size() == 1);
 | |
|     auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(),
 | |
|                                                      FixerReplacements.front());
 | |
|     if (!ChangedCode) {
 | |
|       llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
 | |
|       return 1;
 | |
|     }
 | |
|     llvm::outs() << *ChangedCode;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   // Set up a new source manager for applying the resulting replacements.
 | |
|   IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions);
 | |
|   DiagnosticsEngine Diagnostics(new DiagnosticIDs, &*DiagOpts);
 | |
|   TextDiagnosticPrinter DiagnosticPrinter(outs(), &*DiagOpts);
 | |
|   SourceManager SM(Diagnostics, tool.getFiles());
 | |
|   Diagnostics.setClient(&DiagnosticPrinter, false);
 | |
| 
 | |
|   // Write replacements to disk.
 | |
|   Rewriter Rewrites(SM, LangOptions());
 | |
|   for (const auto &Replacement : FixerReplacements) {
 | |
|     if (!tooling::applyAllReplacements(Replacement, Rewrites)) {
 | |
|       llvm::errs() << "Failed to apply replacements.\n";
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
|   return Rewrites.overwriteChangedFiles();
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| int main(int argc, const char **argv) {
 | |
|   return includeFixerMain(argc, argv);
 | |
| }
 |