r311241 - [clang-diff] Add HTML side-by-side diff output

Johannes Altmanninger via cfe-commits cfe-commits at lists.llvm.org
Sat Aug 19 08:40:45 PDT 2017


Author: krobelus
Date: Sat Aug 19 08:40:45 2017
New Revision: 311241

URL: http://llvm.org/viewvc/llvm-project?rev=311241&view=rev
Log:
[clang-diff] Add HTML side-by-side diff output

Reviewers: arphaman

Subscribers: mgorny

Differential Revision: https://reviews.llvm.org/D36182

Added:
    cfe/trunk/test/Tooling/Inputs/clang-diff-basic-src.cpp
    cfe/trunk/test/Tooling/clang-diff-html.test
Modified:
    cfe/trunk/test/Tooling/clang-diff-basic.cpp
    cfe/trunk/tools/clang-diff/CMakeLists.txt
    cfe/trunk/tools/clang-diff/ClangDiff.cpp

Added: cfe/trunk/test/Tooling/Inputs/clang-diff-basic-src.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Tooling/Inputs/clang-diff-basic-src.cpp?rev=311241&view=auto
==============================================================================
--- cfe/trunk/test/Tooling/Inputs/clang-diff-basic-src.cpp (added)
+++ cfe/trunk/test/Tooling/Inputs/clang-diff-basic-src.cpp Sat Aug 19 08:40:45 2017
@@ -0,0 +1,31 @@
+namespace src {
+
+void foo() {
+  int x = 321;
+}
+
+void main() { foo(); };
+
+const char *a = "foo";
+
+typedef unsigned int nat;
+
+int p = 1 * 2 * 3 * 4;
+int squared = p * p;
+
+class X {
+  const char *foo(int i) {
+    if (i == 0)
+      return "foo";
+    return 0;
+  }
+
+public:
+  X(){};
+
+  int id(int i) { return i; }
+};
+}
+
+void m() { int x = 0 + 0 + 0; }
+int um = 1 + 2 + 3;

Modified: cfe/trunk/test/Tooling/clang-diff-basic.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Tooling/clang-diff-basic.cpp?rev=311241&r1=311240&r2=311241&view=diff
==============================================================================
--- cfe/trunk/test/Tooling/clang-diff-basic.cpp (original)
+++ cfe/trunk/test/Tooling/clang-diff-basic.cpp Sat Aug 19 08:40:45 2017
@@ -1,41 +1,5 @@
-// RUN: %clang_cc1 -E %s > %t.src.cpp
-// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST
-// RUN: clang-diff -dump-matches %t.src.cpp %t.dst.cpp -- | FileCheck %s
+// RUN: clang-diff -dump-matches %S/Inputs/clang-diff-basic-src.cpp %s -- | FileCheck %s
 
-#ifndef DEST
-namespace src {
-
-void foo() {
-  int x = 321;
-}
-
-void main() { foo(); };
-
-const char *a = "foo";
-
-typedef unsigned int nat;
-
-int p = 1 * 2 * 3 * 4;
-int squared = p * p;
-
-class X {
-  const char *foo(int i) {
-    if (i == 0)
-      return "foo";
-    return 0;
-  }
-
-public:
-  X(){};
-
-  int id(int i) { return i; }
-};
-}
-
-void m() { int x = 0 + 0 + 0; }
-int um = 1 + 2 + 3;
-
-#else
 // CHECK: Match TranslationUnitDecl{{.*}} to TranslationUnitDecl
 // CHECK: Match NamespaceDecl: src{{.*}} to NamespaceDecl: dst
 namespace dst {
@@ -82,7 +46,6 @@ class X {
 void m() { { int x = 0 + 0 + 0; } }
 // CHECK: Update and Move IntegerLiteral: 7{{.*}} into BinaryOperator: +({{.*}}) at 1
 int um = 1 + 7;
-#endif
 
 // CHECK: Delete AccessSpecDecl: public
 // CHECK: Delete CXXMethodDecl

Added: cfe/trunk/test/Tooling/clang-diff-html.test
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Tooling/clang-diff-html.test?rev=311241&view=auto
==============================================================================
--- cfe/trunk/test/Tooling/clang-diff-html.test (added)
+++ cfe/trunk/test/Tooling/clang-diff-html.test Sat Aug 19 08:40:45 2017
@@ -0,0 +1,36 @@
+// RUN: clang-diff -html %S/Inputs/clang-diff-basic-src.cpp %S/clang-diff-basic.cpp -- | FileCheck %s
+
+// CHECK: <pre><div id='L' class='code'><span id='L0' tid='R0' title='TranslationUnitDecl
+// CHECK-NEXT: 0 -> 0'>
+
+// match, update
+// CHECK: <span id='L[[L:[0-9]+]]' tid='R[[R:[0-9]+]]' title='NamespaceDecl
+// CHECK-NEXT: [[L]] -> [[R]]
+// CHECK-NEXT: src;' class='u'>namespace src {
+
+// match, move
+// CHECK: <span id='L[[L:[0-9]+]]' tid='R[[R:[0-9]+]]' title='FunctionDecl
+// CHECK-NEXT: [[L]] -> [[R]]
+// CHECK-NEXT: foo(void (void))' class='m'>void foo()
+
+// match
+// CHECK: <span id='L[[L:[0-9]+]]' tid='R[[R:[0-9]+]]' title='FunctionDecl
+// CHECK-NEXT: [[L]] -> [[R]]
+// CHECK-NEXT: main(void (void))'>void main()
+
+// deletion
+// CHECK: <span id='L[[L:[0-9]+]]' tid='R-1' title='IntegerLiteral
+// CHECK-NEXT: [[L]] -> -1
+// CHECK-NEXT: 4' class='d'>4</span>
+
+// update + move
+// CHECK: 2' class='u m'>2</span>
+
+// insertion
+// CHECK: <span id='R[[R:[0-9]+]]' tid='L-1' title='StringLiteral
+// CHECK-NEXT: -1 -> [[R]]
+// CHECK-NEXT: Bar' class='i'>"Bar"</span>
+
+// comments
+// CHECK: // CHECK: Insert IfStmt{{.*}} into IfStmt
+// CHECK: // CHECK: Delete AccessSpecDecl: public

Modified: cfe/trunk/tools/clang-diff/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-diff/CMakeLists.txt?rev=311241&r1=311240&r2=311241&view=diff
==============================================================================
--- cfe/trunk/tools/clang-diff/CMakeLists.txt (original)
+++ cfe/trunk/tools/clang-diff/CMakeLists.txt Sat Aug 19 08:40:45 2017
@@ -7,6 +7,7 @@ add_clang_executable(clang-diff
   )
 
 target_link_libraries(clang-diff
+  clangBasic
   clangFrontend
   clangTooling
   clangToolingASTDiff

Modified: cfe/trunk/tools/clang-diff/ClangDiff.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-diff/ClangDiff.cpp?rev=311241&r1=311240&r2=311241&view=diff
==============================================================================
--- cfe/trunk/tools/clang-diff/ClangDiff.cpp (original)
+++ cfe/trunk/tools/clang-diff/ClangDiff.cpp Sat Aug 19 08:40:45 2017
@@ -37,6 +37,10 @@ static cl::opt<bool>
     PrintMatches("dump-matches", cl::desc("Print the matched nodes."),
                  cl::init(false), cl::cat(ClangDiffCategory));
 
+static cl::opt<bool> HtmlDiff("html",
+                              cl::desc("Output a side-by-side diff in HTML."),
+                              cl::init(false), cl::cat(ClangDiffCategory));
+
 static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
                                        cl::Required,
                                        cl::cat(ClangDiffCategory));
@@ -105,6 +109,161 @@ getAST(const std::unique_ptr<Compilation
 
 static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); }
 
+static const char HtmlDiffHeader[] = R"(
+<html>
+<head>
+<meta charset='utf-8'/>
+<style>
+span.d { color: red; }
+span.u { color: #cc00cc; }
+span.i { color: green; }
+span.m { font-weight: bold; }
+span   { font-weight: normal; color: black; }
+div.code {
+  width: 48%;
+  height: 98%;
+  overflow: scroll;
+  float: left;
+  padding: 0 0 0.5% 0.5%;
+  border: solid 2px LightGrey;
+  border-radius: 5px;
+}
+</style>
+</head>
+<script type='text/javascript'>
+highlightStack = []
+function clearHighlight() {
+  while (highlightStack.length) {
+    let [l, r] = highlightStack.pop()
+    document.getElementById(l).style.backgroundColor = 'white'
+    document.getElementById(r).style.backgroundColor = 'white'
+  }
+}
+function highlight(event) {
+  id = event.target['id']
+  doHighlight(id)
+}
+function doHighlight(id) {
+  clearHighlight()
+  source = document.getElementById(id)
+  if (!source.attributes['tid'])
+    return
+  tid = source.attributes['tid'].value
+  target = document.getElementById(tid)
+  if (!target || source.parentElement && source.parentElement.classList.contains('code'))
+    return
+  source.style.backgroundColor = target.style.backgroundColor = 'lightgrey'
+  highlightStack.push([id, tid])
+  source.scrollIntoView()
+  target.scrollIntoView()
+  location.hash = '#' + id
+}
+function scrollToBoth() {
+  doHighlight(location.hash.substr(1))
+}
+window.onload = scrollToBoth
+</script>
+<body>
+<div onclick='highlight(event)'>
+)";
+
+static void printHtml(raw_ostream &OS, char C) {
+  switch (C) {
+  case '&':
+    OS << "&";
+    break;
+  case '<':
+    OS << "<";
+    break;
+  case '>':
+    OS << ">";
+    break;
+  case '\'':
+    OS << "&#x27;";
+    break;
+  case '"':
+    OS << """;
+    break;
+  default:
+    OS << C;
+  }
+}
+
+static void printHtml(raw_ostream &OS, const StringRef Str) {
+  for (char C : Str)
+    printHtml(OS, C);
+}
+
+static std::string getChangeKindAbbr(diff::ChangeKind Kind) {
+  switch (Kind) {
+  case diff::None:
+    return "";
+  case diff::Delete:
+    return "d";
+  case diff::Update:
+    return "u";
+  case diff::Insert:
+    return "i";
+  case diff::Move:
+    return "m";
+  case diff::UpdateMove:
+    return "u m";
+  }
+}
+
+static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff,
+                                 diff::SyntaxTree &Tree, bool IsLeft,
+                                 diff::NodeId Id, unsigned Offset) {
+  const diff::Node &Node = Tree.getNode(Id);
+  char MyTag, OtherTag;
+  diff::NodeId LeftId, RightId;
+  diff::NodeId TargetId = Diff.getMapped(Tree, Id);
+  if (IsLeft) {
+    MyTag = 'L';
+    OtherTag = 'R';
+    LeftId = Id;
+    RightId = TargetId;
+  } else {
+    MyTag = 'R';
+    OtherTag = 'L';
+    LeftId = TargetId;
+    RightId = Id;
+  }
+  unsigned Begin, End;
+  std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node);
+  const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager();
+  auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer();
+  for (; Offset < Begin; ++Offset)
+    printHtml(OS, Code[Offset]);
+  OS << "<span id='" << MyTag << Id << "' "
+     << "tid='" << OtherTag << TargetId << "' ";
+  OS << "title='";
+  printHtml(OS, Node.getTypeLabel());
+  OS << "\n" << LeftId << " -> " << RightId;
+  std::string Value = Tree.getNodeValue(Node);
+  if (!Value.empty()) {
+    OS << "\n";
+    printHtml(OS, Value);
+  }
+  OS << "'";
+  if (Node.Change != diff::None)
+    OS << " class='" << getChangeKindAbbr(Node.Change) << "'";
+  OS << ">";
+
+  for (diff::NodeId Child : Node.Children)
+    Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset);
+
+  for (; Offset < End; ++Offset)
+    printHtml(OS, Code[Offset]);
+  if (Id == Tree.getRootId()) {
+    End = Code.size();
+    for (; Offset < End; ++Offset)
+      printHtml(OS, Code[Offset]);
+  }
+  OS << "</span>";
+  return Offset;
+}
+
 static void printJsonString(raw_ostream &OS, const StringRef Str) {
   for (signed char C : Str) {
     switch (C) {
@@ -269,6 +428,19 @@ int main(int argc, const char **argv) {
   diff::SyntaxTree DstTree(Dst->getASTContext());
   diff::ASTDiff Diff(SrcTree, DstTree, Options);
 
+  if (HtmlDiff) {
+    llvm::outs() << HtmlDiffHeader << "<pre>";
+    llvm::outs() << "<div id='L' class='code'>";
+    printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0);
+    llvm::outs() << "</div>";
+    llvm::outs() << "<div id='R' class='code'>";
+    printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(),
+                     0);
+    llvm::outs() << "</div>";
+    llvm::outs() << "</pre></div></body></html>\n";
+    return 0;
+  }
+
   for (diff::NodeId Dst : DstTree) {
     diff::NodeId Src = Diff.getMapped(DstTree, Dst);
     if (PrintMatches && Src.isValid()) {




More information about the cfe-commits mailing list