clang API Documentation

FixItRewriter.cpp
Go to the documentation of this file.
00001 //===--- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --*- C++ -*-===//
00002 //
00003 //                     The LLVM Compiler Infrastructure
00004 //
00005 // This file is distributed under the University of Illinois Open Source
00006 // License. See LICENSE.TXT for details.
00007 //
00008 //===----------------------------------------------------------------------===//
00009 //
00010 // This is a diagnostic client adaptor that performs rewrites as
00011 // suggested by code modification hints attached to diagnostics. It
00012 // then forwards any diagnostics to the adapted diagnostic client.
00013 //
00014 //===----------------------------------------------------------------------===//
00015 
00016 #include "clang/Rewrite/Frontend/FixItRewriter.h"
00017 #include "clang/Basic/FileManager.h"
00018 #include "clang/Basic/SourceLocation.h"
00019 #include "clang/Basic/SourceManager.h"
00020 #include "clang/Edit/Commit.h"
00021 #include "clang/Edit/EditsReceiver.h"
00022 #include "clang/Frontend/FrontendDiagnostic.h"
00023 #include "llvm/Support/Path.h"
00024 #include "llvm/Support/raw_ostream.h"
00025 #include <cstdio>
00026 #include <memory>
00027 
00028 using namespace clang;
00029 
00030 FixItRewriter::FixItRewriter(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
00031                              const LangOptions &LangOpts,
00032                              FixItOptions *FixItOpts)
00033   : Diags(Diags),
00034     Editor(SourceMgr, LangOpts),
00035     Rewrite(SourceMgr, LangOpts),
00036     FixItOpts(FixItOpts),
00037     NumFailures(0),
00038     PrevDiagSilenced(false) {
00039   Owner = Diags.takeClient();
00040   Client = Diags.getClient();
00041   Diags.setClient(this, false);
00042 }
00043 
00044 FixItRewriter::~FixItRewriter() {
00045   Diags.setClient(Client, Owner.release() != nullptr);
00046 }
00047 
00048 bool FixItRewriter::WriteFixedFile(FileID ID, raw_ostream &OS) {
00049   const RewriteBuffer *RewriteBuf = Rewrite.getRewriteBufferFor(ID);
00050   if (!RewriteBuf) return true;
00051   RewriteBuf->write(OS);
00052   OS.flush();
00053   return false;
00054 }
00055 
00056 namespace {
00057 
00058 class RewritesReceiver : public edit::EditsReceiver {
00059   Rewriter &Rewrite;
00060 
00061 public:
00062   RewritesReceiver(Rewriter &Rewrite) : Rewrite(Rewrite) { }
00063 
00064   void insert(SourceLocation loc, StringRef text) override {
00065     Rewrite.InsertText(loc, text);
00066   }
00067   void replace(CharSourceRange range, StringRef text) override {
00068     Rewrite.ReplaceText(range.getBegin(), Rewrite.getRangeSize(range), text);
00069   }
00070 };
00071 
00072 }
00073 
00074 bool FixItRewriter::WriteFixedFiles(
00075             std::vector<std::pair<std::string, std::string> > *RewrittenFiles) {
00076   if (NumFailures > 0 && !FixItOpts->FixWhatYouCan) {
00077     Diag(FullSourceLoc(), diag::warn_fixit_no_changes);
00078     return true;
00079   }
00080 
00081   RewritesReceiver Rec(Rewrite);
00082   Editor.applyRewrites(Rec);
00083 
00084   for (iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {
00085     const FileEntry *Entry = Rewrite.getSourceMgr().getFileEntryForID(I->first);
00086     int fd;
00087     std::string Filename = FixItOpts->RewriteFilename(Entry->getName(), fd);
00088     std::error_code EC;
00089     std::unique_ptr<llvm::raw_fd_ostream> OS;
00090     if (fd != -1) {
00091       OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));
00092     } else {
00093       OS.reset(new llvm::raw_fd_ostream(Filename, EC, llvm::sys::fs::F_None));
00094     }
00095     if (EC) {
00096       Diags.Report(clang::diag::err_fe_unable_to_open_output) << Filename
00097                                                               << EC.message();
00098       continue;
00099     }
00100     RewriteBuffer &RewriteBuf = I->second;
00101     RewriteBuf.write(*OS);
00102     OS->flush();
00103 
00104     if (RewrittenFiles)
00105       RewrittenFiles->push_back(std::make_pair(Entry->getName(), Filename));
00106   }
00107 
00108   return false;
00109 }
00110 
00111 bool FixItRewriter::IncludeInDiagnosticCounts() const {
00112   return Client ? Client->IncludeInDiagnosticCounts() : true;
00113 }
00114 
00115 void FixItRewriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
00116                                      const Diagnostic &Info) {
00117   // Default implementation (Warnings/errors count).
00118   DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
00119 
00120   if (!FixItOpts->Silent ||
00121       DiagLevel >= DiagnosticsEngine::Error ||
00122       (DiagLevel == DiagnosticsEngine::Note && !PrevDiagSilenced) ||
00123       (DiagLevel > DiagnosticsEngine::Note && Info.getNumFixItHints())) {
00124     Client->HandleDiagnostic(DiagLevel, Info);
00125     PrevDiagSilenced = false;
00126   } else {
00127     PrevDiagSilenced = true;
00128   }
00129 
00130   // Skip over any diagnostics that are ignored or notes.
00131   if (DiagLevel <= DiagnosticsEngine::Note)
00132     return;
00133   // Skip over errors if we are only fixing warnings.
00134   if (DiagLevel >= DiagnosticsEngine::Error && FixItOpts->FixOnlyWarnings) {
00135     ++NumFailures;
00136     return;
00137   }
00138 
00139   // Make sure that we can perform all of the modifications we
00140   // in this diagnostic.
00141   edit::Commit commit(Editor);
00142   for (unsigned Idx = 0, Last = Info.getNumFixItHints();
00143        Idx < Last; ++Idx) {
00144     const FixItHint &Hint = Info.getFixItHint(Idx);
00145 
00146     if (Hint.CodeToInsert.empty()) {
00147       if (Hint.InsertFromRange.isValid())
00148         commit.insertFromRange(Hint.RemoveRange.getBegin(),
00149                            Hint.InsertFromRange, /*afterToken=*/false,
00150                            Hint.BeforePreviousInsertions);
00151       else
00152         commit.remove(Hint.RemoveRange);
00153     } else {
00154       if (Hint.RemoveRange.isTokenRange() ||
00155           Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd())
00156         commit.replace(Hint.RemoveRange, Hint.CodeToInsert);
00157       else
00158         commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert,
00159                     /*afterToken=*/false, Hint.BeforePreviousInsertions);
00160     }
00161   }
00162   bool CanRewrite = Info.getNumFixItHints() > 0 && commit.isCommitable();
00163 
00164   if (!CanRewrite) {
00165     if (Info.getNumFixItHints() > 0)
00166       Diag(Info.getLocation(), diag::note_fixit_in_macro);
00167 
00168     // If this was an error, refuse to perform any rewriting.
00169     if (DiagLevel >= DiagnosticsEngine::Error) {
00170       if (++NumFailures == 1)
00171         Diag(Info.getLocation(), diag::note_fixit_unfixed_error);
00172     }
00173     return;
00174   }
00175   
00176   if (!Editor.commit(commit)) {
00177     ++NumFailures;
00178     Diag(Info.getLocation(), diag::note_fixit_failed);
00179     return;
00180   }
00181 
00182   Diag(Info.getLocation(), diag::note_fixit_applied);
00183 }
00184 
00185 /// \brief Emit a diagnostic via the adapted diagnostic client.
00186 void FixItRewriter::Diag(SourceLocation Loc, unsigned DiagID) {
00187   // When producing this diagnostic, we temporarily bypass ourselves,
00188   // clear out any current diagnostic, and let the downstream client
00189   // format the diagnostic.
00190   Diags.setClient(Client, false);
00191   Diags.Clear();
00192   Diags.Report(Loc, DiagID);
00193   Diags.setClient(this, false);
00194 }
00195 
00196 FixItOptions::~FixItOptions() {}