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