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()) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits