1 //===--- PlistReporter.cpp - ARC Migrate Tool Plist Reporter ----*- C++ -*-===// 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 #include "Internals.h" 10 #include "clang/Basic/FileManager.h" 11 #include "clang/Basic/PlistSupport.h" 12 #include "clang/Basic/SourceManager.h" 13 #include "clang/Lex/Lexer.h" 14 using namespace clang; 15 using namespace arcmt; 16 using namespace markup; 17 18 static StringRef getLevelName(DiagnosticsEngine::Level Level) { 19 switch (Level) { 20 case DiagnosticsEngine::Ignored: 21 llvm_unreachable("ignored"); 22 case DiagnosticsEngine::Note: 23 return "note"; 24 case DiagnosticsEngine::Remark: 25 case DiagnosticsEngine::Warning: 26 return "warning"; 27 case DiagnosticsEngine::Fatal: 28 case DiagnosticsEngine::Error: 29 return "error"; 30 } 31 llvm_unreachable("Invalid DiagnosticsEngine level!"); 32 } 33 34 void arcmt::writeARCDiagsToPlist(const std::string &outPath, 35 ArrayRef<StoredDiagnostic> diags, 36 SourceManager &SM, 37 const LangOptions &LangOpts) { 38 DiagnosticIDs DiagIDs; 39 40 // Build up a set of FIDs that we use by scanning the locations and 41 // ranges of the diagnostics. 42 FIDMap FM; 43 SmallVector<FileID, 10> Fids; 44 45 for (ArrayRef<StoredDiagnostic>::iterator 46 I = diags.begin(), E = diags.end(); I != E; ++I) { 47 const StoredDiagnostic &D = *I; 48 49 AddFID(FM, Fids, SM, D.getLocation()); 50 51 for (StoredDiagnostic::range_iterator 52 RI = D.range_begin(), RE = D.range_end(); RI != RE; ++RI) { 53 AddFID(FM, Fids, SM, RI->getBegin()); 54 AddFID(FM, Fids, SM, RI->getEnd()); 55 } 56 } 57 58 std::error_code EC; 59 llvm::raw_fd_ostream o(outPath, EC, llvm::sys::fs::OF_TextWithCRLF); 60 if (EC) { 61 llvm::errs() << "error: could not create file: " << outPath << '\n'; 62 return; 63 } 64 65 EmitPlistHeader(o); 66 67 // Write the root object: a <dict> containing... 68 // - "files", an <array> mapping from FIDs to file names 69 // - "diagnostics", an <array> containing the diagnostics 70 o << "<dict>\n" 71 " <key>files</key>\n" 72 " <array>\n"; 73 74 for (FileID FID : Fids) 75 EmitString(o << " ", SM.getFileEntryRefForID(FID)->getName()) << '\n'; 76 77 o << " </array>\n" 78 " <key>diagnostics</key>\n" 79 " <array>\n"; 80 81 for (ArrayRef<StoredDiagnostic>::iterator 82 DI = diags.begin(), DE = diags.end(); DI != DE; ++DI) { 83 84 const StoredDiagnostic &D = *DI; 85 86 if (D.getLevel() == DiagnosticsEngine::Ignored) 87 continue; 88 89 o << " <dict>\n"; 90 91 // Output the diagnostic. 92 o << " <key>description</key>"; 93 EmitString(o, D.getMessage()) << '\n'; 94 o << " <key>category</key>"; 95 EmitString(o, DiagIDs.getCategoryNameFromID( 96 DiagIDs.getCategoryNumberForDiag(D.getID()))) << '\n'; 97 o << " <key>type</key>"; 98 EmitString(o, getLevelName(D.getLevel())) << '\n'; 99 100 // Output the location of the bug. 101 o << " <key>location</key>\n"; 102 EmitLocation(o, SM, D.getLocation(), FM, 2); 103 104 // Output the ranges (if any). 105 if (!D.getRanges().empty()) { 106 o << " <key>ranges</key>\n"; 107 o << " <array>\n"; 108 for (auto &R : D.getRanges()) { 109 CharSourceRange ExpansionRange = SM.getExpansionRange(R); 110 EmitRange(o, SM, Lexer::getAsCharRange(ExpansionRange, SM, LangOpts), 111 FM, 4); 112 } 113 o << " </array>\n"; 114 } 115 116 // Close up the entry. 117 o << " </dict>\n"; 118 } 119 120 o << " </array>\n"; 121 122 // Finish. 123 o << "</dict>\n</plist>\n"; 124 } 125