//===--- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This is a diagnostic client adaptor that performs rewrites as // suggested by code modification hints attached to diagnostics. It // then forwards any diagnostics to the adapted diagnostic client. // //===----------------------------------------------------------------------===// #include "clang/Frontend/FixItRewriter.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "llvm/Support/raw_ostream.h" #include "llvm/System/Path.h" #include "llvm/ADT/OwningPtr.h" #include using namespace clang; FixItRewriter::FixItRewriter(Diagnostic &Diags, SourceManager &SourceMgr, const LangOptions &LangOpts) : Diags(Diags), Rewrite(SourceMgr, LangOpts), NumFailures(0) { Client = Diags.getClient(); Diags.setClient(this); } FixItRewriter::~FixItRewriter() { Diags.setClient(Client); } bool FixItRewriter::WriteFixedFile(const std::string &InFileName, const std::string &OutFileName) { if (NumFailures > 0) { Diag(FullSourceLoc(), diag::warn_fixit_no_changes); return true; } llvm::OwningPtr OwnedStream; llvm::raw_ostream *OutFile; if (!OutFileName.empty()) { std::string Err; OutFile = new llvm::raw_fd_ostream(OutFileName.c_str(), Err, llvm::raw_fd_ostream::F_Binary); OwnedStream.reset(OutFile); } else if (InFileName == "-") { OutFile = &llvm::outs(); } else { llvm::sys::Path Path(InFileName); std::string Suffix = Path.getSuffix(); Path.eraseSuffix(); Path.appendSuffix("fixit." + Suffix); std::string Err; OutFile = new llvm::raw_fd_ostream(Path.c_str(), Err, llvm::raw_fd_ostream::F_Binary); OwnedStream.reset(OutFile); } FileID MainFileID = Rewrite.getSourceMgr().getMainFileID(); if (const RewriteBuffer *RewriteBuf = Rewrite.getRewriteBufferFor(MainFileID)) { *OutFile << std::string(RewriteBuf->begin(), RewriteBuf->end()); } else { Diag(FullSourceLoc(), diag::note_fixit_main_file_unchanged); } OutFile->flush(); return false; } bool FixItRewriter::IncludeInDiagnosticCounts() const { return Client? Client->IncludeInDiagnosticCounts() : true; } void FixItRewriter::HandleDiagnostic(Diagnostic::Level DiagLevel, const DiagnosticInfo &Info) { Client->HandleDiagnostic(DiagLevel, Info); // Skip over any diagnostics that are ignored. if (DiagLevel == Diagnostic::Ignored) return; if (!FixItLocations.empty()) { // The user has specified the locations where we should perform // the various fix-it modifications. // If this diagnostic does not have any code modifications, // completely ignore it, even if it's an error: fix-it locations // are meant to perform specific fix-ups even in the presence of // other errors. if (Info.getNumCodeModificationHints() == 0) return; // See if the location of the error is one that matches what the // user requested. bool AcceptableLocation = false; const FileEntry *File = Rewrite.getSourceMgr().getFileEntryForID( Info.getLocation().getFileID()); unsigned Line = Info.getLocation().getSpellingLineNumber(); unsigned Column = Info.getLocation().getSpellingColumnNumber(); for (llvm::SmallVector::iterator Loc = FixItLocations.begin(), LocEnd = FixItLocations.end(); Loc != LocEnd; ++Loc) { if (Loc->File == File && Loc->Line == Line && Loc->Column == Column) { AcceptableLocation = true; break; } } if (!AcceptableLocation) return; } // Make sure that we can perform all of the modifications we // in this diagnostic. bool CanRewrite = Info.getNumCodeModificationHints() > 0; for (unsigned Idx = 0, Last = Info.getNumCodeModificationHints(); Idx < Last; ++Idx) { const CodeModificationHint &Hint = Info.getCodeModificationHint(Idx); if (Hint.RemoveRange.isValid() && Rewrite.getRangeSize(Hint.RemoveRange) == -1) { CanRewrite = false; break; } if (Hint.InsertionLoc.isValid() && !Rewrite.isRewritable(Hint.InsertionLoc)) { CanRewrite = false; break; } } if (!CanRewrite) { if (Info.getNumCodeModificationHints() > 0) Diag(Info.getLocation(), diag::note_fixit_in_macro); // If this was an error, refuse to perform any rewriting. if (DiagLevel == Diagnostic::Error || DiagLevel == Diagnostic::Fatal) { if (++NumFailures == 1) Diag(Info.getLocation(), diag::note_fixit_unfixed_error); } return; } bool Failed = false; for (unsigned Idx = 0, Last = Info.getNumCodeModificationHints(); Idx < Last; ++Idx) { const CodeModificationHint &Hint = Info.getCodeModificationHint(Idx); if (!Hint.RemoveRange.isValid()) { // We're adding code. if (Rewrite.InsertTextBefore(Hint.InsertionLoc, Hint.CodeToInsert)) Failed = true; continue; } if (Hint.CodeToInsert.empty()) { // We're removing code. if (Rewrite.RemoveText(Hint.RemoveRange.getBegin(), Rewrite.getRangeSize(Hint.RemoveRange))) Failed = true; continue; } // We're replacing code. if (Rewrite.ReplaceText(Hint.RemoveRange.getBegin(), Rewrite.getRangeSize(Hint.RemoveRange), Hint.CodeToInsert)) Failed = true; } if (Failed) { ++NumFailures; Diag(Info.getLocation(), diag::note_fixit_failed); return; } Diag(Info.getLocation(), diag::note_fixit_applied); } /// \brief Emit a diagnostic via the adapted diagnostic client. void FixItRewriter::Diag(FullSourceLoc Loc, unsigned DiagID) { // When producing this diagnostic, we temporarily bypass ourselves, // clear out any current diagnostic, and let the downstream client // format the diagnostic. Diags.setClient(Client); Diags.Clear(); Diags.Report(Loc, DiagID); Diags.setClient(this); }