Index: test/Tooling/Inputs/clang-diff-basic-src.cpp =================================================================== --- /dev/null +++ test/Tooling/Inputs/clang-diff-basic-src.cpp @@ -0,0 +1,28 @@ +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; } +}; +} Index: test/Tooling/clang-diff-basic.cpp =================================================================== --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -1,37 +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; } -}; -} -#else // CHECK: Match TranslationUnitDecl{{.*}} to TranslationUnitDecl // CHECK: Match NamespaceDecl: src{{.*}} to NamespaceDecl: dst namespace dst { @@ -75,4 +43,3 @@ // CHECK: Delete CXXMethodDecl }; } -#endif Index: test/Tooling/clang-diff-html.py =================================================================== --- /dev/null +++ test/Tooling/clang-diff-html.py @@ -0,0 +1,59 @@ +# RUN: clang-diff %S/Inputs/clang-diff-basic-src.cpp %S/clang-diff-basic.cpp -html -- | %python %s > %t.filecheck +# RUN: clang-diff %S/Inputs/clang-diff-basic-src.cpp %S/clang-diff-basic.cpp -dump-matches -- | FileCheck %t.filecheck + +from HTMLParser import HTMLParser +from sys import stdin + +class LeftParser(HTMLParser): + def __init__(self): + HTMLParser.__init__(self) + self.active = False + def handle_starttag(self, tag, attrs): + a = {key: val for key, val in attrs} + if tag == 'div' and a.get('id') == 'L': + self.active = True + return + if not self.active: + return + assert tag == 'span' + if '-1' in a['tid']: + check('Delete %s' % shownode(a)) + + def handle_endtag(self, tag): + if tag == 'div': + self.active = False + +class RightParser(HTMLParser): + def __init__(self): + HTMLParser.__init__(self) + self.active = False + def handle_starttag(self, tag, attrs): + a = {key: val for key, val in attrs} + if tag == 'div' and a.get('id') == 'R': + self.active = True + return + if not self.active: + return + assert tag == 'span' + if '-1' in a['tid']: + check('Insert %s' % shownode(a)) + else: + check('Match {{.*}} to %s' % shownode(a)) + + def handle_endtag(self, tag): + if tag == 'div': + self.active = False + +def check(s): + print 'CHECK:', s + +def shownode(attrdict): + title = attrdict['title'].splitlines() + kind = title[0] + value = '' if len(title) < 3 else (': ' + title[2]) + id = attrdict['id'][1:] + return '%s%s(%s)' % (kind, value, id) + +input = stdin.read() +RightParser().feed(input) +LeftParser().feed(input) Index: tools/clang-diff/CMakeLists.txt =================================================================== --- tools/clang-diff/CMakeLists.txt +++ tools/clang-diff/CMakeLists.txt @@ -7,6 +7,7 @@ ) target_link_libraries(clang-diff + clangBasic clangFrontend clangTooling clangToolingASTDiff Index: tools/clang-diff/ClangDiff.cpp =================================================================== --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -37,6 +37,10 @@ PrintMatches("dump-matches", cl::desc("Print the matched nodes."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt HtmlDiff("html", + cl::desc("Output a side-by-side diff in HTML."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -105,6 +109,161 @@ static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } +static const char HtmlDiffHeader[] = R"( + + + + + + + +
+)"; + +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 << ""; + + 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 << ""; + return Offset; +} + static void printJsonString(raw_ostream &OS, const StringRef Str) { for (char C : Str) { switch (C) { @@ -269,6 +428,19 @@ diff::SyntaxTree DstTree(Dst->getASTContext()); diff::ASTDiff Diff(SrcTree, DstTree, Options); + if (HtmlDiff) { + llvm::outs() << HtmlDiffHeader << "
";
+    llvm::outs() << "
"; + printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); + llvm::outs() << "
"; + llvm::outs() << "
"; + printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(), + 0); + llvm::outs() << "
"; + llvm::outs() << "
\n"; + return 0; + } + for (diff::NodeId Dst : DstTree) { diff::NodeId Src = Diff.getMapped(DstTree, Dst); if (PrintMatches && Src.isValid()) {