vlad.tsyrklevich updated this revision to Diff 90417.
vlad.tsyrklevich edited the summary of this revision.
vlad.tsyrklevich added a comment.

Updated the formatting to make the file split more obvious (padding/line break 
height) and added simple navigation across files, example here: 
https://rawgit.com/vlad902/4aaa4e5e7a777b7098337370791352d7/raw/fc88cef667935e1eeae492b15590c18516e645dd/report.html

@NoQ: I had not thought about deduplication. I looked at scan-build and it 
looks like it currently de-dups based on MD5 matches of the HTML files. The 
simple case of multi-file reports with the same MD5 (e.g. because of repeated 
compilations of a single file during a build) being deduplicated still works 
correctly.

I also looked at what would happen if two separate C files include a common 
header file that only generates a report with -analyzer-opt-analyze-headers 
specified (e.g. if the path originates in the header file and doesn’t traverse 
the main C file at all.) This generated zero reports! My logic assumed the main 
file would always be included in the path and threw away 
-analyzer-opt-analyze-headers reports incorrectly. I was able to fix the logic 
such that these reports are correctly generated and also properly deduplicated 
by structuring the reports such that the main C file source is not included if 
it’s not traversed in the path.

@zaks.anna: I’ve added the single file output option but I would like to keep 
the multi-file option default—I suspect there are very few users parsing the 
HTML manually when plist is available and I’d like to ensure people don’t miss 
any results or have to dig into the source to understand they needed something 
other than the default output setting. What do you think?


https://reviews.llvm.org/D30406

Files:
  include/clang/StaticAnalyzer/Core/Analyses.def
  lib/Rewrite/HTMLRewrite.cpp
  lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
  test/Analysis/diagnostics/diag-cross-file-boundaries.c
  test/Analysis/diagnostics/diag-cross-file-boundaries.h
  test/Analysis/html-diag-singlefile.c
  test/Analysis/html-diag-singlefile.h
  test/Analysis/html-diags-analyze-headers.c
  test/Analysis/html-diags-analyze-headers.h
  test/Analysis/html-diags-multifile.c
  test/Analysis/html-diags.c
  test/Coverage/html-diagnostics.c
  test/Coverage/html-multifile-diagnostics.c
  test/Coverage/html-multifile-diagnostics.h
  www/analyzer/open_projects.html

Index: www/analyzer/open_projects.html
===================================================================
--- www/analyzer/open_projects.html
+++ www/analyzer/open_projects.html
@@ -107,13 +107,6 @@
 
   <li>Bug Reporting 
   <ul>
-    <li>Add support for displaying cross-file diagnostic paths in HTML output
-    (used by <tt>scan-build</tt>).
-    <p>Currently <tt>scan-build</tt> output does not display reports that span 
-    multiple files. The main problem is that we do not have a good format to
-    display such paths in HTML output. <i>(Difficulty: Medium)</i> </p>
-    </li>
-    
     <li>Refactor path diagnostic generation in <a href="http://clang.llvm.org/doxygen/BugReporter_8cpp_source.html";>BugReporter.cpp</a>.
     <p>It would be great to have more code reuse between "Minimal" and 
     "Extensive" PathDiagnostic generation algorithms. One idea is to create an 
Index: test/Coverage/html-multifile-diagnostics.h
===================================================================
--- /dev/null
+++ test/Coverage/html-multifile-diagnostics.h
@@ -0,0 +1,3 @@
+void f1(int *ptr) {
+  *ptr = 0;
+}
Index: test/Coverage/html-multifile-diagnostics.c
===================================================================
--- /dev/null
+++ test/Coverage/html-multifile-diagnostics.c
@@ -0,0 +1,21 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %t %s
+// RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
+
+// REQUIRES: staticanalyzer
+
+// CHECK: <h3>Annotated Source Code</h3>
+
+// Make sure it's generated as multi-file HTML output
+// CHECK: <h4 class=FileName>{{.*}}html-multifile-diagnostics.c</h4>
+// CHECK: <h4 class=FileName>{{.*}}html-multifile-diagnostics.h</h4>
+
+// Without tweaking expr, the expr would hit to the line below
+// emitted to the output as comment.
+// CHECK: {{[D]ereference of null pointer}}
+
+#include "html-multifile-diagnostics.h"
+
+void f0() {
+  f1((int*)0);
+}
Index: test/Coverage/html-diagnostics.c
===================================================================
--- test/Coverage/html-diagnostics.c
+++ test/Coverage/html-diagnostics.c
@@ -1,11 +1,18 @@
 // RUN: rm -rf %t
 // RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %t %s
 // RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
+//
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -analyze -analyzer-output=html-single-file -analyzer-checker=core -o %t %s
+// RUN: find %t -name "*.html" -exec cat "{}" ";" | FileCheck %s
 
 // REQUIRES: staticanalyzer
 
 // CHECK: <h3>Annotated Source Code</h3>
 
+// Make sure it's not generated as a multi-file HTML output
+// CHECK-NOT: <h4 class=FileName>{{.*}}
+
 // Without tweaking expr, the expr would hit to the line below
 // emitted to the output as comment.
 // CHECK: {{[D]ereference of null pointer}}
Index: test/Analysis/html-diags.c
===================================================================
--- test/Analysis/html-diags.c
+++ test/Analysis/html-diags.c
@@ -3,6 +3,12 @@
 // RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %T/dir %s
 // RUN: ls %T/dir | grep report
 
+// D30406: Test new html-single-file output
+// RUN: rm -fR %T/dir
+// RUN: mkdir %T/dir
+// RUN: %clang_cc1 -analyze -analyzer-output=html-single-file -analyzer-checker=core -o %T/dir %s
+// RUN: ls %T/dir | grep report
+
 // PR16547: Test relative paths
 // RUN: cd %T/dir
 // RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o testrelative %s
Index: test/Analysis/html-diags-multifile.c
===================================================================
--- test/Analysis/html-diags-multifile.c
+++ test/Analysis/html-diags-multifile.c
@@ -1,10 +1,9 @@
 // RUN: mkdir -p %t.dir
 // RUN: %clang_cc1 -analyze -analyzer-output=html -analyzer-checker=core -o %t.dir %s
-// RUN: ls %t.dir | not grep report
+// RUN: ls %t.dir | grep report
 // RUN: rm -fR %t.dir
 
-// This tests that we do not currently emit HTML diagnostics for reports that
-// cross file boundaries.
+// This tests that we emit HTML diagnostics for reports that cross file boundaries.
 
 #include "html-diags-multifile.h"
 
Index: test/Analysis/html-diags-analyze-headers.h
===================================================================
--- /dev/null
+++ test/Analysis/html-diags-analyze-headers.h
@@ -0,0 +1,5 @@
+#include "html-diags-multifile.h"
+
+void test_call_macro() {
+  has_bug(0);
+}
Index: test/Analysis/html-diags-analyze-headers.c
===================================================================
--- /dev/null
+++ test/Analysis/html-diags-analyze-headers.c
@@ -0,0 +1,10 @@
+// RUN: mkdir -p %t.dir
+// RUN: %clang_cc1 -analyze -analyzer-opt-analyze-headers -analyzer-output=html -analyzer-checker=core -o %t.dir %s
+// RUN: ls %t.dir | grep report
+// RUN: rm -rf %t.dir
+
+// This tests that we emit HTML diagnostics for reports in headers when the
+// analyzer is run with -analyzer-opt-analyze-headers. This was handled
+// incorrectly in the first iteration of D30406.
+
+#include "html-diags-analyze-headers.h"
Index: test/Analysis/html-diag-singlefile.c
===================================================================
--- /dev/null
+++ test/Analysis/html-diag-singlefile.c
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=core -verify %s
+// RUN: %clang_cc1 -analyze -analyzer-checker=core -analyzer-output=html-single-file -o D30406.html %s 2>&1 | FileCheck %s
+
+// Check that single file HTML output does not process multi-file diagnostics.
+// (This used to test for PR12421, before the introduction of the html-single-file format)
+
+#include "html-diag-singlefile.h"
+
+int main(){
+  f();
+  return 0;
+}
+
+// CHECK: warning: Path diagnostic report is not generated.
Index: test/Analysis/diagnostics/diag-cross-file-boundaries.c
===================================================================
--- test/Analysis/diagnostics/diag-cross-file-boundaries.c
+++ /dev/null
@@ -1,12 +0,0 @@
-// RUN: %clang_cc1 -analyze -analyzer-checker=core -verify %s
-// RUN: %clang_cc1 -analyze -analyzer-checker=core -analyzer-output=html -o PR12421.html %s 2>&1 | FileCheck %s
-
-// Test for PR12421
-#include "diag-cross-file-boundaries.h"
-
-int main(){
-  f();
-  return 0;
-}
-
-// CHECK: warning: Path diagnostic report is not generated.
Index: lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
===================================================================
--- lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -44,8 +44,12 @@
   bool createdDir, noDir;
   const Preprocessor &PP;
   AnalyzerOptions &AnalyzerOpts;
+  const bool SupportsCrossFileDiagnostics;
 public:
-  HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, const Preprocessor &pp);
+  HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
+                  const std::string& prefix,
+                  const Preprocessor &pp,
+                  bool supportsMultipleFiles);
 
   ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
 
@@ -56,6 +60,10 @@
     return "HTMLDiagnostics";
   }
 
+  bool supportsCrossFileDiagnostics() const override {
+    return SupportsCrossFileDiagnostics;
+  }
+
   unsigned ProcessMacroPiece(raw_ostream &os,
                              const PathDiagnosticMacroPiece& P,
                              unsigned num);
@@ -69,21 +77,42 @@
 
   void ReportDiag(const PathDiagnostic& D,
                   FilesMade *filesMade);
+
+  // Rewrite the file specified by FID with HTML formatting.
+  void RewriteFile(Rewriter &R, const SourceManager& SMgr,
+                   const PathPieces& path, FileID FID);
+
+  // Add HTML header/footers to file specified by FID
+  void FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
+                    const SourceManager& SMgr, const PathPieces& path,
+                    FileID FID, const FileEntry *Entry, const char *declName);
 };
 
 } // end anonymous namespace
 
 HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
                                  const std::string& prefix,
-                                 const Preprocessor &pp)
-    : Directory(prefix), createdDir(false), noDir(false), PP(pp), AnalyzerOpts(AnalyzerOpts) {
-}
+                                 const Preprocessor &pp,
+                                 bool supportsMultipleFiles)
+    : Directory(prefix),
+      createdDir(false),
+      noDir(false),
+      PP(pp),
+      AnalyzerOpts(AnalyzerOpts),
+      SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
 
 void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
                                         PathDiagnosticConsumers &C,
                                         const std::string& prefix,
                                         const Preprocessor &PP) {
-  C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP));
+  C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true));
+}
+
+void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
+                                                  PathDiagnosticConsumers &C,
+                                                  const std::string& prefix,
+                                                  const Preprocessor &PP) {
+  C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false));
 }
 
 //===----------------------------------------------------------------------===//
@@ -121,24 +150,24 @@
   // First flatten out the entire path to make it easier to use.
   PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
 
-  // The path as already been prechecked that all parts of the path are
-  // from the same file and that it is non-empty.
-  const SourceManager &SMgr = path.front()->getLocation().getManager();
+  // The path as already been prechecked that the path is non-empty.
   assert(!path.empty());
-  FileID FID =
-    path.front()->getLocation().asLocation().getExpansionLoc().getFileID();
-  assert(FID.isValid());
+  const SourceManager &SMgr = path.front()->getLocation().getManager();
 
   // Create a new rewriter to generate HTML.
   Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
 
+  // The file for the first path element is considered the main report file, it
+  // will usually be equivalent to SMgr.getMainFileID(); however, it might be a
+  // header when -analyzer-opt-analyze-headers is used.
+  FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID();
+
   // Get the function/method name
   SmallString<128> declName("unknown");
   int offsetDecl = 0;
   if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
-      if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
+      if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue))
           declName = ND->getDeclName().getAsString();
-      }
 
       if (const Stmt *Body = DeclWithIssue->getBody()) {
           // Retrieve the relative position of the declaration which will be used
@@ -151,49 +180,133 @@
       }
   }
 
-  // Process the path.
-  // Maintain the counts of extra note pieces separately.
-  unsigned TotalPieces = path.size();
-  unsigned TotalNotePieces =
-      std::count_if(path.begin(), path.end(),
-                    [](const std::shared_ptr<PathDiagnosticPiece> &p) {
-                      return isa<PathDiagnosticNotePiece>(*p);
-                    });
+  // Rewrite source files as HTML for every new file the path crosses
+  std::vector<FileID> FileIDs;
+  for (auto I = path.begin(), E = path.end(); I != E; ++I) {
+    FileID FID = (*I)->getLocation().asLocation().getExpansionLoc().getFileID();
+    if (std::find(FileIDs.begin(), FileIDs.end(), FID) != FileIDs.end())
+      continue;
 
-  unsigned TotalRegularPieces = TotalPieces - TotalNotePieces;
-  unsigned NumRegularPieces = TotalRegularPieces;
-  unsigned NumNotePieces = TotalNotePieces;
+    FileIDs.push_back(FID);
+    RewriteFile(R, SMgr, path, FID);
+  }
 
-  for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
-    if (isa<PathDiagnosticNotePiece>(I->get())) {
-      // This adds diagnostic bubbles, but not navigation.
-      // Navigation through note pieces would be added later,
-      // as a separate pass through the piece list.
-      HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces);
-      --NumNotePieces;
-    } else {
-      HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces);
-      --NumRegularPieces;
+  if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
+    // Prefix file names to every file if this is a multi-file report
+    for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
+      SourceLocation StartLoc = SMgr.getLocForStartOfFile(*I);
+      const FileEntry* Entry = SMgr.getFileEntryForID(*I);
+
+      std::string s;
+      llvm::raw_string_ostream os(s);
+      os << "<div id=File" << I->getHashValue() << ">\n";
+
+      if (I != FileIDs.begin()) {
+        os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
+           << "\">&#x2190;</a></div>";
+      }
+
+      os << "<h4 class=FileName>" << Entry->getName() << "</h4>\n";
+
+      if (I + 1 != E) {
+        os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
+           << "\">&#x2192;</a></div>";
+      }
+
+      os << "</div>\n";
+      R.InsertTextBefore(StartLoc, os.str());
+    }
+
+    // Then concatenate the HTML for all files in this path to the end of the
+    // ReportFile HTML in order of which they appear in the path.
+    for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
+      if (*I == ReportFile)
+        continue;
+
+      const RewriteBuffer *Buf = R.getRewriteBufferFor(*I);
+
+      std::string file;
+      llvm::raw_string_ostream o(file);
+      o << "<hr class=divider>\n";
+      for (RewriteBuffer::iterator BI = Buf->begin(), BE = Buf->end(); BI!=BE; ++BI)
+        o << *BI;
+
+      SourceLocation MainFileEndLoc = SMgr.getLocForEndOfFile(ReportFile);
+      R.InsertTextAfter(MainFileEndLoc, o.str());
     }
   }
 
-  // Add line numbers, header, footer, etc.
+  // Get the rewrite buffer.
+  const RewriteBuffer *Buf = R.getRewriteBufferFor(ReportFile);
+  if (!Buf) {
+    llvm::errs() << "warning: no diagnostics generated for main file.\n";
+    return;
+  }
 
-  // unsigned FID = R.getSourceMgr().getMainFileID();
-  html::EscapeText(R, FID);
-  html::AddLineNumbers(R, FID);
+  // Add CSS, header, and footer.
+  const FileEntry* Entry = SMgr.getFileEntryForID(ReportFile);
+  FinalizeHTML(D, R, SMgr, path, ReportFile, Entry, declName.c_str());
 
-  // If we have a preprocessor, relex the file and syntax highlight.
-  // We might not have a preprocessor if we come from a deserialized AST file,
-  // for example.
+  // Create a path for the target HTML file.
+  int FD;
+  SmallString<128> Model, ResultPath;
 
-  html::SyntaxHighlight(R, FID, PP);
-  html::HighlightMacros(R, FID, PP);
+  if (!AnalyzerOpts.shouldWriteStableReportFilename()) {
+      llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
+      if (std::error_code EC =
+          llvm::sys::fs::make_absolute(Model)) {
+          llvm::errs() << "warning: could not make '" << Model
+                       << "' absolute: " << EC.message() << '\n';
+        return;
+      }
+      if (std::error_code EC =
+          llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) {
+          llvm::errs() << "warning: could not create file in '" << Directory
+                       << "': " << EC.message() << '\n';
+          return;
+      }
+
+  } else {
+      int i = 1;
+      std::error_code EC;
+      do {
+          // Find a filename which is not already used
+          std::stringstream filename;
+          Model = "";
+          filename << "report-"
+                   << llvm::sys::path::filename(Entry->getName()).str()
+                   << "-" << declName.c_str()
+                   << "-" << offsetDecl
+                   << "-" << i << ".html";
+          llvm::sys::path::append(Model, Directory,
+                                  filename.str());
+          EC = llvm::sys::fs::openFileForWrite(Model,
+                                               FD,
+                                               llvm::sys::fs::F_RW |
+                                               llvm::sys::fs::F_Excl);
+          if (EC && EC != llvm::errc::file_exists) {
+              llvm::errs() << "warning: could not create file '" << Model
+                           << "': " << EC.message() << '\n';
+              return;
+          }
+          i++;
+      } while (EC);
+  }
 
-  // Get the full directory name of the analyzed file.
+  llvm::raw_fd_ostream os(FD, true);
 
-  const FileEntry* Entry = SMgr.getFileEntryForID(FID);
+  if (filesMade)
+    filesMade->addDiagnostic(D, getName(),
+                             llvm::sys::path::filename(ResultPath));
 
+  // Emit the HTML to disk.
+  for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I)
+      os << *I;
+}
+
+void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
+    const SourceManager& SMgr, const PathPieces& path, FileID FID,
+    const FileEntry *Entry, const char *declName) {
   // This is a cludge; basically we want to append either the full
   // working directory if we have no directory information.  This is
   // a work in progress.
@@ -306,73 +419,48 @@
     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
   }
 
-  // Add CSS, header, and footer.
-
   html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName());
+}
 
-  // Get the rewrite buffer.
-  const RewriteBuffer *Buf = R.getRewriteBufferFor(FID);
-
-  if (!Buf) {
-    llvm::errs() << "warning: no diagnostics generated for main file.\n";
-    return;
-  }
-
-  // Create a path for the target HTML file.
-  int FD;
-  SmallString<128> Model, ResultPath;
+void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr,
+    const PathPieces& path, FileID FID) {
+  // Process the path.
+  // Maintain the counts of extra note pieces separately.
+  unsigned TotalPieces = path.size();
+  unsigned TotalNotePieces =
+      std::count_if(path.begin(), path.end(),
+                    [](const std::shared_ptr<PathDiagnosticPiece> &p) {
+                      return isa<PathDiagnosticNotePiece>(*p);
+                    });
 
-  if (!AnalyzerOpts.shouldWriteStableReportFilename()) {
-      llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
-      if (std::error_code EC =
-          llvm::sys::fs::make_absolute(Model)) {
-          llvm::errs() << "warning: could not make '" << Model
-                       << "' absolute: " << EC.message() << '\n';
-        return;
-      }
-      if (std::error_code EC =
-          llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) {
-          llvm::errs() << "warning: could not create file in '" << Directory
-                       << "': " << EC.message() << '\n';
-          return;
-      }
+  unsigned TotalRegularPieces = TotalPieces - TotalNotePieces;
+  unsigned NumRegularPieces = TotalRegularPieces;
+  unsigned NumNotePieces = TotalNotePieces;
 
-  } else {
-      int i = 1;
-      std::error_code EC;
-      do {
-          // Find a filename which is not already used
-          std::stringstream filename;
-          Model = "";
-          filename << "report-"
-                   << llvm::sys::path::filename(Entry->getName()).str()
-                   << "-" << declName.c_str()
-                   << "-" << offsetDecl
-                   << "-" << i << ".html";
-          llvm::sys::path::append(Model, Directory,
-                                  filename.str());
-          EC = llvm::sys::fs::openFileForWrite(Model,
-                                               FD,
-                                               llvm::sys::fs::F_RW |
-                                               llvm::sys::fs::F_Excl);
-          if (EC && EC != llvm::errc::file_exists) {
-              llvm::errs() << "warning: could not create file '" << Model
-                           << "': " << EC.message() << '\n';
-              return;
-          }
-          i++;
-      } while (EC);
+  for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
+    if (isa<PathDiagnosticNotePiece>(I->get())) {
+      // This adds diagnostic bubbles, but not navigation.
+      // Navigation through note pieces would be added later,
+      // as a separate pass through the piece list.
+      HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces);
+      --NumNotePieces;
+    } else {
+      HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces);
+      --NumRegularPieces;
+    }
   }
 
-  llvm::raw_fd_ostream os(FD, true);
+  // Add line numbers, header, footer, etc.
 
-  if (filesMade)
-    filesMade->addDiagnostic(D, getName(),
-                             llvm::sys::path::filename(ResultPath));
+  html::EscapeText(R, FID);
+  html::AddLineNumbers(R, FID);
 
-  // Emit the HTML to disk.
-  for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I)
-      os << *I;
+  // If we have a preprocessor, relex the file and syntax highlight.
+  // We might not have a preprocessor if we come from a deserialized AST file,
+  // for example.
+
+  html::SyntaxHighlight(R, FID, PP);
+  html::HighlightMacros(R, FID, PP);
 }
 
 void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID,
Index: lib/Rewrite/HTMLRewrite.cpp
===================================================================
--- lib/Rewrite/HTMLRewrite.cpp
+++ lib/Rewrite/HTMLRewrite.cpp
@@ -289,6 +289,11 @@
       " body { color:#000000; background-color:#ffffff }\n"
       " body { font-family:Helvetica, sans-serif; font-size:10pt }\n"
       " h1 { font-size:14pt }\n"
+      " .FileName { margin-top: 5px; margin-bottom: 5px; display: inline; }\n"
+      " .FileNav { margin-left: 5px; margin-right: 5px; display: inline; }\n"
+      " .FileNav a { text-decoration:none; font-size: larger; }\n"
+      " .divider { margin-top: 30px; margin-bottom: 30px; height: 15px; }\n"
+      " .divider { background-color: gray; }\n"
       " .code { border-collapse:collapse; width:100%; }\n"
       " .code { font-family: \"Monospace\", monospace; font-size:10pt }\n"
       " .code { line-height: 1.2em }\n"
Index: include/clang/StaticAnalyzer/Core/Analyses.def
===================================================================
--- include/clang/StaticAnalyzer/Core/Analyses.def
+++ include/clang/StaticAnalyzer/Core/Analyses.def
@@ -27,9 +27,10 @@
 #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN)
 #endif
 
-ANALYSIS_DIAGNOSTICS(HTML,  "html",  "Output analysis results using HTML",   createHTMLDiagnosticConsumer)
+ANALYSIS_DIAGNOSTICS(HTML, "html", "Output analysis results using HTML", createHTMLDiagnosticConsumer)
+ANALYSIS_DIAGNOSTICS(HTML_SINGLE_FILE, "html-single-file", "Output analysis results using HTML (not allowing for multi-file bugs)", createHTMLSingleFileDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(PLIST, "plist", "Output analysis results using Plists", createPlistDiagnosticConsumer)
-ANALYSIS_DIAGNOSTICS(PLIST_MULTI_FILE, "plist-multi-file", "Output analysis results using Plists (allowing for mult-file bugs)", createPlistMultiFileDiagnosticConsumer)
+ANALYSIS_DIAGNOSTICS(PLIST_MULTI_FILE, "plist-multi-file", "Output analysis results using Plists (allowing for multi-file bugs)", createPlistMultiFileDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(PLIST_HTML, "plist-html", "Output analysis results using HTML wrapped with Plists", createPlistHTMLDiagnosticConsumer)
 ANALYSIS_DIAGNOSTICS(TEXT, "text", "Text output of analysis results", createTextPathDiagnosticConsumer)
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to