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 =
99        FixItOpts->RewriteFilename(std::string(Entry->getName()), fd);
100    std::error_code EC;
101    std::unique_ptr<llvm::raw_fd_ostream> OS;
102    if (fd != -1) {
103      OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));
104    } else {
105      OS.reset(new llvm::raw_fd_ostream(Filename, EC, llvm::sys::fs::OF_None));
106    }
107    if (EC) {
108      Diags.Report(clang::diag::err_fe_unable_to_open_output) << Filename
109                                                              << EC.message();
110      continue;
111    }
112    RewriteBuffer &RewriteBuf = I->second;
113    RewriteBuf.write(*OS);
114    OS->flush();
115
116    if (RewrittenFiles)
117      RewrittenFiles->push_back(
118          std::make_pair(std::string(Entry->getName()), Filename));
119  }
120
121  return false;
122}
123
124bool FixItRewriter::IncludeInDiagnosticCounts() const {
125  return Client ? Client->IncludeInDiagnosticCounts() : true;
126}
127
128void FixItRewriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
129                                     const Diagnostic &Info) {
130  // Default implementation (Warnings/errors count).
131  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
132
133  if (!FixItOpts->Silent ||
134      DiagLevel >= DiagnosticsEngine::Error ||
135      (DiagLevel == DiagnosticsEngine::Note && !PrevDiagSilenced) ||
136      (DiagLevel > DiagnosticsEngine::Note && Info.getNumFixItHints())) {
137    Client->HandleDiagnostic(DiagLevel, Info);
138    PrevDiagSilenced = false;
139  } else {
140    PrevDiagSilenced = true;
141  }
142
143  // Skip over any diagnostics that are ignored or notes.
144  if (DiagLevel <= DiagnosticsEngine::Note)
145    return;
146  // Skip over errors if we are only fixing warnings.
147  if (DiagLevel >= DiagnosticsEngine::Error && FixItOpts->FixOnlyWarnings) {
148    ++NumFailures;
149    return;
150  }
151
152  // Make sure that we can perform all of the modifications we
153  // in this diagnostic.
154  edit::Commit commit(Editor);
155  for (unsigned Idx = 0, Last = Info.getNumFixItHints();
156       Idx < Last; ++Idx) {
157    const FixItHint &Hint = Info.getFixItHint(Idx);
158
159    if (Hint.CodeToInsert.empty()) {
160      if (Hint.InsertFromRange.isValid())
161        commit.insertFromRange(Hint.RemoveRange.getBegin(),
162                           Hint.InsertFromRange, /*afterToken=*/false,
163                           Hint.BeforePreviousInsertions);
164      else
165        commit.remove(Hint.RemoveRange);
166    } else {
167      if (Hint.RemoveRange.isTokenRange() ||
168          Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd())
169        commit.replace(Hint.RemoveRange, Hint.CodeToInsert);
170      else
171        commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert,
172                    /*afterToken=*/false, Hint.BeforePreviousInsertions);
173    }
174  }
175  bool CanRewrite = Info.getNumFixItHints() > 0 && commit.isCommitable();
176
177  if (!CanRewrite) {
178    if (Info.getNumFixItHints() > 0)
179      Diag(Info.getLocation(), diag::note_fixit_in_macro);
180
181    // If this was an error, refuse to perform any rewriting.
182    if (DiagLevel >= DiagnosticsEngine::Error) {
183      if (++NumFailures == 1)
184        Diag(Info.getLocation(), diag::note_fixit_unfixed_error);
185    }
186    return;
187  }
188
189  if (!Editor.commit(commit)) {
190    ++NumFailures;
191    Diag(Info.getLocation(), diag::note_fixit_failed);
192    return;
193  }
194
195  Diag(Info.getLocation(), diag::note_fixit_applied);
196}
197
198/// Emit a diagnostic via the adapted diagnostic client.
199void FixItRewriter::Diag(SourceLocation Loc, unsigned DiagID) {
200  // When producing this diagnostic, we temporarily bypass ourselves,
201  // clear out any current diagnostic, and let the downstream client
202  // format the diagnostic.
203  Diags.setClient(Client, false);
204  Diags.Clear();
205  Diags.Report(Loc, DiagID);
206  Diags.setClient(this, false);
207}
208
209FixItOptions::~FixItOptions() = default;
210