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