Path: blob/main/contrib/llvm-project/clang/lib/Frontend/Rewrite/FixItRewriter.cpp
35266 views
//===- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This is a diagnostic client adaptor that performs rewrites as9// suggested by code modification hints attached to diagnostics. It10// then forwards any diagnostics to the adapted diagnostic client.11//12//===----------------------------------------------------------------------===//1314#include "clang/Rewrite/Frontend/FixItRewriter.h"15#include "clang/Basic/Diagnostic.h"16#include "clang/Basic/FileManager.h"17#include "clang/Basic/LLVM.h"18#include "clang/Basic/SourceLocation.h"19#include "clang/Basic/SourceManager.h"20#include "clang/Edit/Commit.h"21#include "clang/Edit/EditsReceiver.h"22#include "clang/Frontend/FrontendDiagnostic.h"23#include "clang/Rewrite/Core/RewriteBuffer.h"24#include "clang/Rewrite/Core/Rewriter.h"25#include "llvm/ADT/StringRef.h"26#include "llvm/Support/FileSystem.h"27#include "llvm/Support/raw_ostream.h"28#include <cstdio>29#include <memory>30#include <string>31#include <system_error>32#include <utility>3334using namespace clang;3536FixItRewriter::FixItRewriter(DiagnosticsEngine &Diags, SourceManager &SourceMgr,37const LangOptions &LangOpts,38FixItOptions *FixItOpts)39: Diags(Diags), Editor(SourceMgr, LangOpts), Rewrite(SourceMgr, LangOpts),40FixItOpts(FixItOpts) {41Owner = Diags.takeClient();42Client = Diags.getClient();43Diags.setClient(this, false);44}4546FixItRewriter::~FixItRewriter() {47Diags.setClient(Client, Owner.release() != nullptr);48}4950bool FixItRewriter::WriteFixedFile(FileID ID, raw_ostream &OS) {51const RewriteBuffer *RewriteBuf = Rewrite.getRewriteBufferFor(ID);52if (!RewriteBuf) return true;53RewriteBuf->write(OS);54OS.flush();55return false;56}5758namespace {5960class RewritesReceiver : public edit::EditsReceiver {61Rewriter &Rewrite;6263public:64RewritesReceiver(Rewriter &Rewrite) : Rewrite(Rewrite) {}6566void insert(SourceLocation loc, StringRef text) override {67Rewrite.InsertText(loc, text);68}6970void replace(CharSourceRange range, StringRef text) override {71Rewrite.ReplaceText(range.getBegin(), Rewrite.getRangeSize(range), text);72}73};7475} // namespace7677bool FixItRewriter::WriteFixedFiles(78std::vector<std::pair<std::string, std::string>> *RewrittenFiles) {79if (NumFailures > 0 && !FixItOpts->FixWhatYouCan) {80Diag(FullSourceLoc(), diag::warn_fixit_no_changes);81return true;82}8384RewritesReceiver Rec(Rewrite);85Editor.applyRewrites(Rec);8687if (FixItOpts->InPlace) {88// Overwriting open files on Windows is tricky, but the rewriter can do it89// for us.90Rewrite.overwriteChangedFiles();91return false;92}9394for (iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {95OptionalFileEntryRef Entry =96Rewrite.getSourceMgr().getFileEntryRefForID(I->first);97int fd;98std::string Filename =99FixItOpts->RewriteFilename(std::string(Entry->getName()), fd);100std::error_code EC;101std::unique_ptr<llvm::raw_fd_ostream> OS;102if (fd != -1) {103OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));104} else {105OS.reset(new llvm::raw_fd_ostream(Filename, EC, llvm::sys::fs::OF_None));106}107if (EC) {108Diags.Report(clang::diag::err_fe_unable_to_open_output) << Filename109<< EC.message();110continue;111}112RewriteBuffer &RewriteBuf = I->second;113RewriteBuf.write(*OS);114OS->flush();115116if (RewrittenFiles)117RewrittenFiles->push_back(118std::make_pair(std::string(Entry->getName()), Filename));119}120121return false;122}123124bool FixItRewriter::IncludeInDiagnosticCounts() const {125return Client ? Client->IncludeInDiagnosticCounts() : true;126}127128void FixItRewriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,129const Diagnostic &Info) {130// Default implementation (Warnings/errors count).131DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);132133if (!FixItOpts->Silent ||134DiagLevel >= DiagnosticsEngine::Error ||135(DiagLevel == DiagnosticsEngine::Note && !PrevDiagSilenced) ||136(DiagLevel > DiagnosticsEngine::Note && Info.getNumFixItHints())) {137Client->HandleDiagnostic(DiagLevel, Info);138PrevDiagSilenced = false;139} else {140PrevDiagSilenced = true;141}142143// Skip over any diagnostics that are ignored or notes.144if (DiagLevel <= DiagnosticsEngine::Note)145return;146// Skip over errors if we are only fixing warnings.147if (DiagLevel >= DiagnosticsEngine::Error && FixItOpts->FixOnlyWarnings) {148++NumFailures;149return;150}151152// Make sure that we can perform all of the modifications we153// in this diagnostic.154edit::Commit commit(Editor);155for (unsigned Idx = 0, Last = Info.getNumFixItHints();156Idx < Last; ++Idx) {157const FixItHint &Hint = Info.getFixItHint(Idx);158159if (Hint.CodeToInsert.empty()) {160if (Hint.InsertFromRange.isValid())161commit.insertFromRange(Hint.RemoveRange.getBegin(),162Hint.InsertFromRange, /*afterToken=*/false,163Hint.BeforePreviousInsertions);164else165commit.remove(Hint.RemoveRange);166} else {167if (Hint.RemoveRange.isTokenRange() ||168Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd())169commit.replace(Hint.RemoveRange, Hint.CodeToInsert);170else171commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert,172/*afterToken=*/false, Hint.BeforePreviousInsertions);173}174}175bool CanRewrite = Info.getNumFixItHints() > 0 && commit.isCommitable();176177if (!CanRewrite) {178if (Info.getNumFixItHints() > 0)179Diag(Info.getLocation(), diag::note_fixit_in_macro);180181// If this was an error, refuse to perform any rewriting.182if (DiagLevel >= DiagnosticsEngine::Error) {183if (++NumFailures == 1)184Diag(Info.getLocation(), diag::note_fixit_unfixed_error);185}186return;187}188189if (!Editor.commit(commit)) {190++NumFailures;191Diag(Info.getLocation(), diag::note_fixit_failed);192return;193}194195Diag(Info.getLocation(), diag::note_fixit_applied);196}197198/// 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 client202// format the diagnostic.203Diags.setClient(Client, false);204Diags.Clear();205Diags.Report(Loc, DiagID);206Diags.setClient(this, false);207}208209FixItOptions::~FixItOptions() = default;210211212