src/FixItExporter.cpp (155 lines of code) (raw):

/* SPDX-FileCopyrightText: 2019 Christian Gagneraud <chgans@gmail.com> SPDX-License-Identifier: LGPL-2.0-or-later */ #include "FixItExporter.h" #include <clang/Basic/SourceManager.h> #include <clang/Frontend/FrontendDiagnostic.h> #include <clang/Rewrite/Frontend/FixItRewriter.h> #include <clang/Tooling/DiagnosticsYaml.h> // #define DEBUG_FIX_IT_EXPORTER using namespace clang; static clang::tooling::TranslationUnitDiagnostics &getTuDiag() { static clang::tooling::TranslationUnitDiagnostics s_tudiag; return s_tudiag; } FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, const LangOptions &LangOpts, const std::string &exportFixes, bool isClazyStandalone) : DiagEngine(DiagEngine) , SourceMgr(SourceMgr) , LangOpts(LangOpts) , exportFixes(exportFixes) { if (!isClazyStandalone) { // When using clazy as plugin each translation unit fixes goes to a separate YAML file getTuDiag().Diagnostics.clear(); } Owner = DiagEngine.takeClient(); Client = DiagEngine.getClient(); DiagEngine.setClient(this, false); } FixItExporter::~FixItExporter() { if (Client) { DiagEngine.setClient(Client, Owner.release() != nullptr); } } void FixItExporter::BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP) { if (Client) { Client->BeginSourceFile(LangOpts, PP); } const auto id = SourceMgr.getMainFileID(); const auto entry = SourceMgr.getFileEntryRefForID(id); getTuDiag().MainSourceFile = entry->getName().str(); } bool FixItExporter::IncludeInDiagnosticCounts() const { return Client ? Client->IncludeInDiagnosticCounts() : true; } void FixItExporter::EndSourceFile() { if (Client) { Client->EndSourceFile(); } } tooling::Diagnostic FixItExporter::ConvertDiagnostic(const Diagnostic &Info) { SmallString<256> TmpMessageText; Info.FormatDiagnostic(TmpMessageText); // TODO: This returns an empty string: DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(Info.getID()); // HACK: capture it at the end of the message: Message text [check-name] std::string checkName = static_cast<std::string>(DiagEngine.getDiagnosticIDs()->getWarningOptionForDiag(Info.getID())); std::string messageText; if (checkName.empty()) { // Non-built-in clang warnings have the [checkName] in the message messageText = TmpMessageText.slice(0, TmpMessageText.find_last_of('[') - 1).str(); checkName = TmpMessageText.slice(TmpMessageText.find_last_of('[') + 3, TmpMessageText.find_last_of(']')).str(); } else { messageText = TmpMessageText.c_str(); } llvm::StringRef CurrentBuildDir; // Not needed? tooling::Diagnostic ToolingDiag(checkName, tooling::Diagnostic::Warning, CurrentBuildDir); // FIXME: Sometimes the file path is an empty string. if (Info.getLocation().isMacroID()) { auto MacroLoc = SourceMgr.getFileLoc(Info.getLocation()); ToolingDiag.Message = tooling::DiagnosticMessage(messageText, SourceMgr, MacroLoc); } else { ToolingDiag.Message = tooling::DiagnosticMessage(messageText, SourceMgr, Info.getLocation()); } return ToolingDiag; } tooling::Replacement FixItExporter::ConvertFixIt(const FixItHint &Hint) { // TODO: Proper handling of macros // https://stackoverflow.com/questions/24062989/clang-fails-replacing-a-statement-if-it-contains-a-macro tooling::Replacement Replacement; if (Hint.CodeToInsert.empty()) { if (Hint.InsertFromRange.isValid()) { clang::SourceLocation b(Hint.InsertFromRange.getBegin()); clang::SourceLocation _e(Hint.InsertFromRange.getEnd()); if (b.isMacroID()) { b = SourceMgr.getSpellingLoc(b); } if (_e.isMacroID()) { _e = SourceMgr.getSpellingLoc(_e); } clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts)); StringRef Text(SourceMgr.getCharacterData(b), SourceMgr.getCharacterData(e) - SourceMgr.getCharacterData(b)); return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text); } return tooling::Replacement(SourceMgr, Hint.RemoveRange, ""); } return tooling::Replacement(SourceMgr, Hint.RemoveRange, Hint.CodeToInsert); } void FixItExporter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { // Default implementation (Warnings/errors count). DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); // Let original client do it's handling if (Client) { Client->HandleDiagnostic(DiagLevel, Info); } // Convert and record warning diagnostics and their notes if (DiagLevel == DiagnosticsEngine::Warning) { auto ToolingDiag = ConvertDiagnostic(Info); for (unsigned Idx = 0, Last = Info.getNumFixItHints(); Idx < Last; ++Idx) { const FixItHint &Hint = Info.getFixItHint(Idx); const auto replacement = ConvertFixIt(Hint); #ifdef DEBUG_FIX_IT_EXPORTER const auto FileName = SourceMgr.getFilename(Info.getLocation()); llvm::errs() << "Handling Fixit #" << Idx << " for " << FileName.str() << "\n"; llvm::errs() << "F: " << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " << Hint.BeforePreviousInsertions << " " << Hint.CodeToInsert << "\n"; llvm::errs() << "R: " << replacement.toString() << "\n"; #endif clang::tooling::Replacements &Replacements = ToolingDiag.Message.Fix[replacement.getFilePath()]; llvm::Error error = Replacements.add(ConvertFixIt(Hint)); if (error) { Diag(Info.getLocation(), diag::note_fixit_failed); } } getTuDiag().Diagnostics.push_back(ToolingDiag); m_recordNotes = true; } // FIXME: We do not receive notes. else if (DiagLevel == DiagnosticsEngine::Note && m_recordNotes) { #ifdef DEBUG_FIX_IT_EXPORTER const auto FileName = SourceMgr.getFilename(Info.getLocation()); llvm::errs() << "Handling Note for " << FileName.str() << "\n"; #endif auto diags = getTuDiag().Diagnostics.back(); auto diag = ConvertDiagnostic(Info); diags.Notes.append(1, diag.Message); } else { m_recordNotes = false; } } void FixItExporter::Export() { auto tuDiag = getTuDiag(); if (!tuDiag.Diagnostics.empty()) { std::error_code EC; llvm::raw_fd_ostream OS(exportFixes, EC, llvm::sys::fs::OF_None); llvm::yaml::Output YAML(OS); YAML << getTuDiag(); } } void FixItExporter::Diag(SourceLocation Loc, unsigned DiagID) { // When producing this diagnostic, we temporarily bypass ourselves, // and let the downstream client format the diagnostic. DiagEngine.setClient(Client, false); #if LLVM_VERSION_MAJOR < 20 DiagEngine.Clear(); #endif DiagEngine.Report(Loc, DiagID); DiagEngine.setClient(this, false); }