diff --git a/.gitignore b/.gitignore
index 38c2d41a5320a8a92a093e7bb7b074bff3b7bf20..508f0b516e108de57e4a924d70e6657608265651 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,6 +78,7 @@ bin/qtcreator.exe
 share/doc/qtcreator/qtcreator.qch
 tests/manual/cplusplus/cplusplus0
 tests/manual/cplusplus-dump/cplusplus0
+tests/manual/qml-ast2dot/qml-ast2dot
 tests/auto/qml/qmldesigner/bauhaustests/tst_bauhaus
 tests/auto/qml/qmldesigner/coretests/tst_qmldesigner_core
 tests/auto/qml/qmldesigner/propertyeditortests/tst_propertyeditor
diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri
index 753619790f0c16ade33e8de332da156b2f25b39e..e471af494581ec15e85bf4d917c26f9e60cdcdf9 100644
--- a/src/libs/qmljs/qmljs-lib.pri
+++ b/src/libs/qmljs/qmljs-lib.pri
@@ -5,7 +5,6 @@ contains(CONFIG, dll) {
 }
 
 include(parser/parser.pri)
-include(../utils/utils.pri)
 
 DEPENDPATH += $$PWD
 INCLUDEPATH += $$PWD/..
diff --git a/src/libs/qmljs/qmljs.pro b/src/libs/qmljs/qmljs.pro
index 468de26f64c6468b288efba8d408a419bfc302ec..1fe566f970e268a58abe7088c37f673a616ebcaa 100644
--- a/src/libs/qmljs/qmljs.pro
+++ b/src/libs/qmljs/qmljs.pro
@@ -7,3 +7,4 @@ unix:QMAKE_CXXFLAGS_DEBUG += -O3
 
 include(../../qtcreatorlibrary.pri)
 include(qmljs-lib.pri)
+include(../utils/utils.pri)
diff --git a/tests/manual/qml-ast2dot/main.cpp b/tests/manual/qml-ast2dot/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d0c06dea4391afa653547fb86e06743d5fabd6b0
--- /dev/null
+++ b/tests/manual/qml-ast2dot/main.cpp
@@ -0,0 +1,301 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include <qmljs/parser/qmljsast_p.h>
+#include <qmljs/parser/qmljsastvisitor_p.h>
+#include <qmljs/qmljsdocument.h>
+
+#include <QFile>
+#include <QList>
+#include <QCoreApplication>
+#include <QStringList>
+#include <QFileInfo>
+#include <QTime>
+#include <QtDebug>
+
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#ifdef __GNUC__
+#  include <cxxabi.h>
+#endif
+
+using namespace QmlJS;
+using namespace QmlJS::AST;
+using namespace std;
+
+class ASTDump: protected Visitor
+{
+public:
+    void operator()(const QString &fileName, const QByteArray &src, Node *ast) {
+        _src = src;
+        QString basename = fileName;
+        int dotIdx = basename.lastIndexOf('.');
+        if (dotIdx != -1)
+            basename.truncate(dotIdx);
+        basename.append(QLatin1String(".ast.dot"));
+        out.open(basename.toUtf8().constData());
+
+        out << "digraph AST { ordering=out;" << endl;
+        // cout << "rankdir = \"LR\";" << endl;
+        Node::accept(ast, this);
+
+        typedef QPair<QByteArray, QByteArray> Pair;
+
+        foreach (const Pair &conn, _connections)
+            out << conn.first.constData() << " -> " << conn.second.constData() << endl;
+
+        alignTerminals();
+
+        out << "}" << endl;
+        out.close();
+        cout << qPrintable(basename) << endl;
+    }
+
+protected: // visiting methods:
+    virtual bool visit(UiImport *ast) {
+        terminal(ast->importToken);
+
+        if (ast->importUri)
+            nonterminal(ast->importUri);
+        else
+            terminal(ast->fileNameToken);
+
+        terminal(ast->versionToken);
+        terminal(ast->asToken);
+        terminal(ast->importIdToken);
+        terminal(ast->semicolonToken);
+        return false;
+    }
+
+    virtual bool visit(UiObjectBinding *ast) {
+        if (ast->hasOnToken) {
+            nonterminal(ast->qualifiedTypeNameId);
+            terminal(ast->colonToken);
+            nonterminal(ast->qualifiedId);
+        } else {
+            nonterminal(ast->qualifiedId);
+            terminal(ast->colonToken);
+            nonterminal(ast->qualifiedTypeNameId);
+        }
+        nonterminal(ast->initializer);
+        return false;
+    }
+
+    virtual bool visit(UiObjectDefinition *ast) {
+        nonterminal(ast->qualifiedTypeNameId);
+        nonterminal(ast->initializer);
+        return false;
+    }
+
+    virtual bool visit(UiObjectInitializer *ast) {
+        terminal(ast->lbraceToken);
+        nonterminal(ast->members);
+        terminal(ast->rbraceToken);
+        return false;
+    }
+
+    virtual bool visit(UiScriptBinding *ast) {
+        nonterminal(ast->qualifiedId);
+        terminal(ast->colonToken);
+        nonterminal(ast->statement);
+        return false;
+    }
+
+    virtual bool visit(UiArrayBinding *ast) {
+        nonterminal(ast->qualifiedId);
+        terminal(ast->colonToken);
+        terminal(ast->lbracketToken);
+        nonterminal(ast->members);
+        terminal(ast->rbracketToken);
+        return false;
+    }
+
+    virtual bool visit(UiArrayMemberList *ast) {
+        terminal(ast->commaToken);
+        nonterminal(ast->member);
+        nonterminal(ast->next);
+        return false;
+    }
+
+    virtual bool visit(UiQualifiedId *ast) {
+        terminal(ast->identifierToken);
+        nonterminal(ast->next);
+        return false;
+    }
+
+    virtual bool visit(UiPublicMember *ast) {
+        // TODO: place the paramters...
+//        UiParameterList *parameters;
+
+        terminal(ast->defaultToken);
+        terminal(ast->readonlyToken);
+        terminal(ast->propertyToken);
+        terminal(ast->typeModifierToken);
+        terminal(ast->typeToken);
+        terminal(ast->identifierToken);
+        terminal(ast->colonToken);
+        nonterminal(ast->expression);
+        nonterminal(ast->binding);
+        terminal(ast->semicolonToken);
+        return false;
+    }
+
+    virtual bool visit(StringLiteral *ast) { terminal(ast->literalToken); return false; }
+    virtual bool visit(NumericLiteral *ast) { terminal(ast->literalToken); return false; }
+    virtual bool visit(TrueLiteral *ast) { terminal(ast->trueToken); return false; }
+    virtual bool visit(FalseLiteral *ast) { terminal(ast->falseToken); return false; }
+    virtual bool visit(IdentifierExpression *ast) { terminal(ast->identifierToken); return false; }
+    virtual bool visit(FieldMemberExpression *ast) { nonterminal(ast->base); terminal(ast->dotToken); terminal(ast->identifierToken); return false; }
+    virtual bool visit(BinaryExpression *ast) { nonterminal(ast->left); terminal(ast->operatorToken); nonterminal(ast->right); return false; }
+    virtual bool visit(UnaryPlusExpression *ast) { terminal(ast->plusToken); nonterminal(ast->expression); return false; }
+    virtual bool visit(UnaryMinusExpression *ast) { terminal(ast->minusToken); nonterminal(ast->expression); return false; }
+    virtual bool visit(NestedExpression *ast) { terminal(ast->lparenToken); nonterminal(ast->expression); terminal(ast->rparenToken); return false; }
+
+protected:
+    void alignTerminals() {
+        out<<"{ rank=same;" << endl;
+        foreach (const QByteArray &terminalShape, _terminalShapes) {
+            out << "  " << string(terminalShape) << ";" << endl;
+        }
+        out<<"}"<<endl;
+    }
+
+    static QByteArray name(Node *ast) {
+#ifdef __GNUC__
+        QByteArray name = abi::__cxa_demangle(typeid(*ast).name(), 0, 0, 0) + 12;
+#else
+        QByteArray name = typeid(*ast).name();
+#endif
+        return name;
+    }
+
+    QString spell(const SourceLocation &token) {
+        return _src.mid(token.offset, token.length).replace('\'', "\\\\").replace('"', "\\\"");
+    }
+
+    void terminal(const SourceLocation &token) {
+        if (!token.isValid())
+            return;
+
+        static int count = 1;
+        QByteArray id = 't' + QByteArray::number(count++);
+        Node *node = _stack.last();
+        _connections.append(qMakePair(_id[node], id));
+
+        QByteArray t;
+        t.append(id);
+        t.append(" [label = \"");
+        t.append(spell(token));
+        t.append("\" shape=rect]");
+        _terminalShapes.append(t);
+    }
+
+    virtual void nonterminal(Node *ast) {
+        Node::accept(ast, this);
+    }
+
+    virtual void node(Node *ast) {
+        out << _id[ast].constData() << " [label=\"" << name(ast).constData() << "\"];" << endl;
+    }
+
+    virtual bool preVisit(Node *ast) {
+        static int count = 1;
+        const QByteArray id = 'n' + QByteArray::number(count++);
+        _id[ast] = id;
+
+
+        if (! _stack.isEmpty())
+            _connections.append(qMakePair(_id[_stack.last()], id));
+
+        _stack.append(ast);
+
+        node(ast);
+
+        return true;
+    }
+
+    virtual void postVisit(Node *) {
+        _stack.removeLast();
+    }
+
+private:
+    QHash<Node *, QByteArray> _id;
+    QList<QPair<QByteArray, QByteArray> > _connections;
+    QList<Node *> _stack;
+    QList<QByteArray> _terminalShapes;
+    ofstream out;
+    QByteArray _src;
+};
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    QStringList files = app.arguments();
+    files.removeFirst();
+
+    foreach (const QString &fileName, files) {
+        QFile file(fileName);
+        if (! file.open(QFile::ReadOnly)) {
+            cerr << "Cannot open \"" << qPrintable(fileName)
+                      << "\", skipping it." << endl;
+            continue;
+        }
+
+        const QByteArray source = file.readAll();
+        file.close();
+
+        Document::Ptr doc = Document::create(fileName);
+        doc->setSource(source);
+        doc->parseQml();
+
+        foreach (const DiagnosticMessage &m, doc->diagnosticMessages()) {
+            ostream *os;
+            if (m.isError()) {
+                os = &cerr;
+                *os << "Error:";
+            } else {
+                os = &cout;
+                *os << "Warning:";
+            }
+
+            if (m.loc.isValid())
+                *os << m.loc.startLine << ':' << m.loc.startColumn << ':';
+            *os << ' ';
+            *os << qPrintable(m.message) << endl;
+        }
+
+        ASTDump dump;
+        dump(fileName, source, doc->qmlProgram());
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/tests/manual/qml-ast2dot/qml-ast2dot.pro b/tests/manual/qml-ast2dot/qml-ast2dot.pro
new file mode 100644
index 0000000000000000000000000000000000000000..7ee4c66f623ee32a1b7fcf46bf49b5ec00f468c5
--- /dev/null
+++ b/tests/manual/qml-ast2dot/qml-ast2dot.pro
@@ -0,0 +1,22 @@
+QT = core gui
+macx:CONFIG -= app_bundle
+TARGET = qml-ast2dot
+
+include(../../../src/libs/qmljs/qmljs-lib.pri)
+
+SOURCES += ../../../src/libs/utils/changeset.cpp
+HEADERS += ../../../src/libs/utils/changeset.h
+
+# Input
+SOURCES += main.cpp
+
+unix {
+    debug:OBJECTS_DIR = $${OUT_PWD}/.obj/debug-shared
+    release:OBJECTS_DIR = $${OUT_PWD}/.obj/release-shared
+
+    debug:MOC_DIR = $${OUT_PWD}/.moc/debug-shared
+    release:MOC_DIR = $${OUT_PWD}/.moc/release-shared
+
+    RCC_DIR = $${OUT_PWD}/.rcc/
+    UI_DIR = $${OUT_PWD}/.uic/
+}