1//===--- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --*- C++ -*-===//
2//
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// This is a diagnostic client adaptor that performs rewrites as
11// suggested by code modification hints attached to diagnostics. It
12// then forwards any diagnostics to the adapted diagnostic client.
13//
14//===----------------------------------------------------------------------===//
15
16#include "clang/Rewrite/FixItRewriter.h"
17#include "clang/Edit/Commit.h"
18#include "clang/Edit/EditsReceiver.h"
19#include "clang/Basic/FileManager.h"
20#include "clang/Basic/SourceLocation.h"
21#include "clang/Basic/SourceManager.h"
22#include "clang/Frontend/FrontendDiagnostic.h"
23#include "llvm/Support/raw_ostream.h"
24#include "llvm/Support/Path.h"
25#include "llvm/ADT/OwningPtr.h"
26#include <cstdio>
27
28using namespace clang;
29
30FixItRewriter::FixItRewriter(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
31                             const LangOptions &LangOpts,
32                             FixItOptions *FixItOpts)
33  : Diags(Diags),
34    Editor(SourceMgr, LangOpts),
35    Rewrite(SourceMgr, LangOpts),
36    FixItOpts(FixItOpts),
37    NumFailures(0),
38    PrevDiagSilenced(false) {
39  OwnsClient = Diags.ownsClient();
40  Client = Diags.takeClient();
41  Diags.setClient(this);
42}
43
44FixItRewriter::~FixItRewriter() {
45  Diags.takeClient();
46  Diags.setClient(Client, OwnsClient);
47}
48
49bool FixItRewriter::WriteFixedFile(FileID ID, raw_ostream &OS) {
50  const RewriteBuffer *RewriteBuf = Rewrite.getRewriteBufferFor(ID);
51  if (!RewriteBuf) return true;
52  RewriteBuf->write(OS);
53  OS.flush();
54  return false;
55}
56
57namespace {
58
59class RewritesReceiver : public edit::EditsReceiver {
60  Rewriter &Rewrite;
61
62public:
63  RewritesReceiver(Rewriter &Rewrite) : Rewrite(Rewrite) { }
64
65  virtual void insert(SourceLocation loc, StringRef text) {
66    Rewrite.InsertText(loc, text);
67  }
68  virtual void replace(CharSourceRange range, StringRef text) {
69    Rewrite.ReplaceText(range.getBegin(), Rewrite.getRangeSize(range), text);
70  }
71};
72
73}
74
75bool FixItRewriter::WriteFixedFiles(
76            std::vector<std::pair<std::string, std::string> > *RewrittenFiles) {
77  if (NumFailures > 0 && !FixItOpts->FixWhatYouCan) {
78    Diag(FullSourceLoc(), diag::warn_fixit_no_changes);
79    return true;
80  }
81
82  RewritesReceiver Rec(Rewrite);
83  Editor.applyRewrites(Rec);
84
85  for (iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {
86    const FileEntry *Entry = Rewrite.getSourceMgr().getFileEntryForID(I->first);
87    int fd;
88    std::string Filename = FixItOpts->RewriteFilename(Entry->getName(), fd);
89    std::string Err;
90    OwningPtr<llvm::raw_fd_ostream> OS;
91    if (fd != -1) {
92      OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));
93    } else {
94      OS.reset(new llvm::raw_fd_ostream(Filename.c_str(), Err,
95                                        llvm::raw_fd_ostream::F_Binary));
96    }
97    if (!Err.empty()) {
98      Diags.Report(clang::diag::err_fe_unable_to_open_output)
99          << Filename << Err;
100      continue;
101    }
102    RewriteBuffer &RewriteBuf = I->second;
103    RewriteBuf.write(*OS);
104    OS->flush();
105
106    if (RewrittenFiles)
107      RewrittenFiles->push_back(std::make_pair(Entry->getName(), Filename));
108  }
109
110  return false;
111}
112
113bool FixItRewriter::IncludeInDiagnosticCounts() const {
114  return Client ? Client->IncludeInDiagnosticCounts() : true;
115}
116
117void FixItRewriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
118                                     const Diagnostic &Info) {
119  // Default implementation (Warnings/errors count).
120  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
121
122  if (!FixItOpts->Silent ||
123      DiagLevel >= DiagnosticsEngine::Error ||
124      (DiagLevel == DiagnosticsEngine::Note && !PrevDiagSilenced) ||
125      (DiagLevel > DiagnosticsEngine::Note && Info.getNumFixItHints())) {
126    Client->HandleDiagnostic(DiagLevel, Info);
127    PrevDiagSilenced = false;
128  } else {
129    PrevDiagSilenced = true;
130  }
131
132  // Skip over any diagnostics that are ignored or notes.
133  if (DiagLevel <= DiagnosticsEngine::Note)
134    return;
135  // Skip over errors if we are only fixing warnings.
136  if (DiagLevel >= DiagnosticsEngine::Error && FixItOpts->FixOnlyWarnings) {
137    ++NumFailures;
138    return;
139  }
140
141  // Make sure that we can perform all of the modifications we
142  // in this diagnostic.
143  edit::Commit commit(Editor);
144  for (unsigned Idx = 0, Last = Info.getNumFixItHints();
145       Idx < Last; ++Idx) {
146    const FixItHint &Hint = Info.getFixItHint(Idx);
147
148    if (Hint.CodeToInsert.empty()) {
149      if (Hint.InsertFromRange.isValid())
150        commit.insertFromRange(Hint.RemoveRange.getBegin(),
151                           Hint.InsertFromRange, /*afterToken=*/false,
152                           Hint.BeforePreviousInsertions);
153      else
154        commit.remove(Hint.RemoveRange);
155    } else {
156      if (Hint.RemoveRange.isTokenRange() ||
157          Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd())
158        commit.replace(Hint.RemoveRange, Hint.CodeToInsert);
159      else
160        commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert,
161                    /*afterToken=*/false, Hint.BeforePreviousInsertions);
162    }
163  }
164  bool CanRewrite = Info.getNumFixItHints() > 0 && commit.isCommitable();
165
166  if (!CanRewrite) {
167    if (Info.getNumFixItHints() > 0)
168      Diag(Info.getLocation(), diag::note_fixit_in_macro);
169
170    // If this was an error, refuse to perform any rewriting.
171    if (DiagLevel >= DiagnosticsEngine::Error) {
172      if (++NumFailures == 1)
173        Diag(Info.getLocation(), diag::note_fixit_unfixed_error);
174    }
175    return;
176  }
177
178  if (!Editor.commit(commit)) {
179    ++NumFailures;
180    Diag(Info.getLocation(), diag::note_fixit_failed);
181    return;
182  }
183
184  Diag(Info.getLocation(), diag::note_fixit_applied);
185}
186
187/// \brief Emit a diagnostic via the adapted diagnostic client.
188void FixItRewriter::Diag(SourceLocation Loc, unsigned DiagID) {
189  // When producing this diagnostic, we temporarily bypass ourselves,
190  // clear out any current diagnostic, and let the downstream client
191  // format the diagnostic.
192  Diags.takeClient();
193  Diags.setClient(Client);
194  Diags.Clear();
195  Diags.Report(Loc, DiagID);
196  Diags.takeClient();
197  Diags.setClient(this);
198}
199
200DiagnosticConsumer *FixItRewriter::clone(DiagnosticsEngine &Diags) const {
201  return new FixItRewriter(Diags, Diags.getSourceManager(),
202                           Rewrite.getLangOpts(), FixItOpts);
203}
204
205FixItOptions::~FixItOptions() {}
206