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 << "'";
+ 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