diff --git a/src/plugins/qmljsinspector/qmljsdelta.cpp b/src/plugins/qmljsinspector/qmljsdelta.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3770d647f2dd6abbb051dfee98bff59a807d12df --- /dev/null +++ b/src/plugins/qmljsinspector/qmljsdelta.cpp @@ -0,0 +1,439 @@ +/************************************************************************** +** +** 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 "qmljsdelta.h" +#include "qmljsclientproxy.h" +#include <qmljs/parser/qmljsast_p.h> +#include <qmljs/parser/qmljsastvisitor_p.h> + +#include <QtCore/QDebug> + +using namespace QmlJS; +using namespace QmlJS::AST; +using namespace QmlJSInspector::Internal; + +namespace { + +class ScriptBindingParser : protected Visitor +{ + QList<UiObjectMember *> objectStack; + QHash<UiScriptBinding *, UiObjectMember *> _parent; + QHash<UiObjectMember *, UiScriptBinding *> _id; + QHash<UiScriptBinding *, QDeclarativeDebugObjectReference> _idBindings; + +public: + + QmlJS::Document::Ptr doc; + QList<UiScriptBinding *> scripts; + + ScriptBindingParser(Document::Ptr doc, const QList<QDeclarativeDebugObjectReference> &objectReferences = QList<QDeclarativeDebugObjectReference>()); + void process(); + + UiObjectMember *parent(UiScriptBinding *script) const + { return _parent.value(script); } + + UiScriptBinding *id(UiObjectMember *parent) const + { return _id.value(parent); } + + QList<UiScriptBinding *> ids() const + { return _id.values(); } + + QDeclarativeDebugObjectReference objectReferenceForPosition(const QUrl &url, int line, int col) const; + + QDeclarativeDebugObjectReference objectReferenceForScriptBinding(UiScriptBinding *binding) const; + + QDeclarativeDebugObjectReference objectReferenceForOffset(unsigned int offset); + + QString header(UiObjectMember *member) const + { + if (member) { + if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) { + const int begin = def->firstSourceLocation().begin(); + const int end = def->initializer->lbraceToken.begin(); + return doc->source().mid(begin, end - begin); + } else if (UiObjectBinding *binding = cast<UiObjectBinding *>(member)) { + const int begin = binding->firstSourceLocation().begin(); + const int end = binding->initializer->lbraceToken.begin(); + return doc->source().mid(begin, end - begin); + } + } + + return QString(); + } + + QString scriptCode(UiScriptBinding *script) const + { + if (script) { + const int begin = script->statement->firstSourceLocation().begin(); + const int end = script->statement->lastSourceLocation().end(); + return doc->source().mid(begin, end - begin); + } + + return QString(); + } + +protected: + QDeclarativeDebugObjectReference objectReference(const QString &id) const; + + virtual bool visit(UiObjectDefinition *ast); + virtual void endVisit(UiObjectDefinition *); + virtual bool visit(UiObjectBinding *ast); + virtual void endVisit(UiObjectBinding *); + virtual bool visit(UiScriptBinding *ast); + +private: + QList<QDeclarativeDebugObjectReference> objectReferences; + QDeclarativeDebugObjectReference m_foundObjectReference; + unsigned int m_searchElementOffset; + +}; + + +} // end of anonymous namespace + +static bool isLiteralValue(ExpressionNode *expr) +{ + if (cast<NumericLiteral*>(expr)) + return true; + else if (cast<StringLiteral*>(expr)) + return true; + else if (UnaryPlusExpression *plusExpr = cast<UnaryPlusExpression*>(expr)) + return isLiteralValue(plusExpr->expression); + else if (UnaryMinusExpression *minusExpr = cast<UnaryMinusExpression*>(expr)) + return isLiteralValue(minusExpr->expression); + else if (cast<TrueLiteral*>(expr)) + return true; + else if (cast<FalseLiteral*>(expr)) + return true; + else + return false; +} + +static inline bool isLiteralValue(UiScriptBinding *script) +{ + if (!script || !script->statement) + return false; + + ExpressionStatement *exprStmt = cast<ExpressionStatement *>(script->statement); + if (exprStmt) + return isLiteralValue(exprStmt->expression); + else + return false; +} + +static inline QString stripQuotes(const QString &str) +{ + if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) + || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) + return str.mid(1, str.length() - 2); + + return str; +} + +static inline QString deEscape(const QString &value) +{ + QString result = value; + + result.replace(QLatin1String("\\\\"), QLatin1String("\\")); + result.replace(QLatin1String("\\\""), QLatin1String("\"")); + result.replace(QLatin1String("\\\t"), QLatin1String("\t")); + result.replace(QLatin1String("\\\r"), QLatin1String("\\\r")); + result.replace(QLatin1String("\\\n"), QLatin1String("\n")); + + return result; +} + +static QString cleanExpression(const QString &expression, UiScriptBinding *scriptBinding) +{ + QString trimmedExpression = expression.trimmed(); + + if (ExpressionStatement *expStatement = cast<ExpressionStatement*>(scriptBinding->statement)) { + if (expStatement->semicolonToken.isValid()) + trimmedExpression.chop(1); + } + + return deEscape(stripQuotes(trimmedExpression)); +} + +static QVariant castToLiteral(const QString &expression, UiScriptBinding *scriptBinding) +{ + const QString cleanedValue = cleanExpression(expression, scriptBinding); + QVariant castedExpression; + + ExpressionStatement *expStatement = cast<ExpressionStatement*>(scriptBinding->statement); + + switch(expStatement->expression->kind) { + case Node::Kind_NumericLiteral: + case Node::Kind_UnaryPlusExpression: + case Node::Kind_UnaryMinusExpression: + castedExpression = QVariant(cleanedValue).toReal(); + break; + case Node::Kind_StringLiteral: + castedExpression = QVariant(cleanedValue).toString(); + break; + case Node::Kind_TrueLiteral: + case Node::Kind_FalseLiteral: + castedExpression = QVariant(cleanedValue).toBool(); + break; + default: + castedExpression = cleanedValue; + break; + } + + return castedExpression; +} + +ScriptBindingParser::ScriptBindingParser(QmlJS::Document::Ptr doc, + const QList<QDeclarativeDebugObjectReference> &objectReferences) + : doc(doc), objectReferences(objectReferences), m_searchElementOffset(-1) +{ + +} + +void ScriptBindingParser::process() +{ + if (!doc.isNull() && doc->qmlProgram()) + doc->qmlProgram()->accept(this); +} + +QDeclarativeDebugObjectReference ScriptBindingParser::objectReferenceForOffset(unsigned int offset) +{ + m_searchElementOffset = offset; + if (!doc.isNull() && doc->qmlProgram()) + doc->qmlProgram()->accept(this); + + return m_foundObjectReference; +} + +QDeclarativeDebugObjectReference ScriptBindingParser::objectReference(const QString &id) const +{ + foreach (const QDeclarativeDebugObjectReference &ref, objectReferences) { + if (ref.idString() == id) + return ref; + } + + return QDeclarativeDebugObjectReference(); +} + + +QDeclarativeDebugObjectReference ScriptBindingParser::objectReferenceForPosition(const QUrl &url, int line, int col) const +{ + foreach (const QDeclarativeDebugObjectReference &ref, objectReferences) { + if (ref.source().lineNumber() == line + && ref.source().columnNumber() == col + && ref.source().url() == url) + { + return ref; + } + } + + return QDeclarativeDebugObjectReference(); +} + +bool ScriptBindingParser::visit(UiObjectDefinition *ast) +{ + objectStack.append(ast); + return true; +} + +void ScriptBindingParser::endVisit(UiObjectDefinition *) +{ + objectStack.removeLast(); +} + +bool ScriptBindingParser::visit(UiObjectBinding *ast) +{ + objectStack.append(ast); + return true; +} + +void ScriptBindingParser::endVisit(UiObjectBinding *) +{ + objectStack.removeLast(); +} + +bool ScriptBindingParser::visit(UiScriptBinding *ast) +{ + scripts.append(ast); + _parent[ast] = objectStack.back(); + + if (ast->qualifiedId && ast->qualifiedId->name && ! ast->qualifiedId->next) { + const QString bindingName = ast->qualifiedId->name->asString(); + + if (bindingName == QLatin1String("id")) { + _id[objectStack.back()] = ast; + + if (ExpressionStatement *s = cast<ExpressionStatement *>(ast->statement)) { + if (IdentifierExpression *id = cast<IdentifierExpression *>(s->expression)) { + if (id->name) { + _idBindings.insert(ast, objectReference(id->name->asString())); + + if (parent(ast)->firstSourceLocation().offset == m_searchElementOffset) + m_foundObjectReference = objectReference(id->name->asString()); + } + } + } + } + } + + return true; +} + +QDeclarativeDebugObjectReference ScriptBindingParser::objectReferenceForScriptBinding(UiScriptBinding *binding) const +{ + return _idBindings.value(binding); +} + +// Delta + +static QString propertyName(UiQualifiedId *id) +{ + QString s; + + for (; id; id = id->next) { + if (! id->name) + return QString(); + + s += id->name->asString(); + + if (id->next) + s += QLatin1Char('.'); + } + + return s; +} + +void Delta::operator()(Document::Ptr doc, Document::Ptr previousDoc) +{ + const QUrl url = QUrl::fromLocalFile(doc->fileName()); + ScriptBindingParser bindingParser(doc, ClientProxy::instance()->objectReferences(url)); + bindingParser.process(); + ScriptBindingParser previousBindingParser(previousDoc, ClientProxy::instance()->objectReferences(url)); + previousBindingParser.process(); + + QHash<UiObjectMember *, UiObjectMember *> preservedObjects; + + foreach (UiScriptBinding *id, bindingParser.ids()) { + UiObjectMember *parent = bindingParser.parent(id); + const QString idCode = bindingParser.scriptCode(id); + + foreach (UiScriptBinding *otherId, previousBindingParser.ids()) { + const QString otherIdCode = previousBindingParser.scriptCode(otherId); + + if (idCode == otherIdCode) { + preservedObjects.insert(parent, previousBindingParser.parent(otherId)); + } + } + } + + QHashIterator<UiObjectMember *, UiObjectMember *> it(preservedObjects); + while (it.hasNext()) { + it.next(); + + UiObjectMember *object = it.key(); + UiObjectMember *previousObject = it.value(); + + for (UiObjectMemberList *objectMemberIt = objectMembers(object); objectMemberIt; objectMemberIt = objectMemberIt->next) { + if (UiScriptBinding *script = cast<UiScriptBinding *>(objectMemberIt->member)) { + for (UiObjectMemberList *previousObjectMemberIt = objectMembers(previousObject); previousObjectMemberIt; previousObjectMemberIt = previousObjectMemberIt->next) { + if (UiScriptBinding *previousScript = cast<UiScriptBinding *>(previousObjectMemberIt->member)) { + if (compare(script->qualifiedId, previousScript->qualifiedId)) { + const QString scriptCode = bindingParser.scriptCode(script); + const QString previousScriptCode = previousBindingParser.scriptCode(previousScript); + + if (scriptCode != previousScriptCode) { + const QString property = propertyName(script->qualifiedId); + qDebug() << "property:" << qPrintable(property) << scriptCode; + + if (UiScriptBinding *idBinding = bindingParser.id(object)) { + if (ExpressionStatement *s = cast<ExpressionStatement *>(idBinding->statement)) { + if (IdentifierExpression *idExpr = cast<IdentifierExpression *>(s->expression)) { + const QString idString = idExpr->name->asString(); + qDebug() << "the enclosing object id is:" << idString; + + const QList<QDeclarativeDebugObjectReference> refs = ClientProxy::instance()->objectReferences(url); + foreach (const QDeclarativeDebugObjectReference &ref, refs) { + if (ref.idString() == idString) { + updateScriptBinding(ref, script, property, scriptCode); + break; + } + } + } + } + } + + } + } + } + } + } + } + + } +} + +void Delta::updateScriptBinding(const QDeclarativeDebugObjectReference &objectReference, + UiScriptBinding *scriptBinding, const QString &propertyName, const QString &scriptCode) +{ + qDebug() << "update script:" << propertyName << scriptCode << scriptBinding; + + QVariant expr = scriptCode; + //qDebug() << " " << scriptBinding->statement->kind << typeid(*scriptBinding->statement).name(); + + bool isLiteral = isLiteralValue(scriptBinding); + if (isLiteral) + expr = castToLiteral(scriptCode, scriptBinding); + + ClientProxy::instance()->setBindingForObject(objectReference.debugId(), propertyName, expr, isLiteral); +} + +bool Delta::compare(UiQualifiedId *id, UiQualifiedId *other) +{ + if (id == other) + return true; + + else if (id && other) { + if (id->name && other->name) { + if (id->name->asString() == other->name->asString()) + return compare(id->next, other->next); + } + } + + return false; +} + +UiObjectMemberList *Delta::objectMembers(UiObjectMember *object) +{ + if (UiObjectDefinition *def = cast<UiObjectDefinition *>(object)) + return def->initializer->members; + else if (UiObjectBinding *binding = cast<UiObjectBinding *>(object)) + return binding->initializer->members; + + return 0; +} diff --git a/src/plugins/qmljsinspector/qmljsdelta.h b/src/plugins/qmljsinspector/qmljsdelta.h new file mode 100644 index 0000000000000000000000000000000000000000..7cb9288057091a022cde9d330ca8a357c9df64cf --- /dev/null +++ b/src/plugins/qmljsinspector/qmljsdelta.h @@ -0,0 +1,60 @@ +/************************************************************************** +** +** 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. +** +**************************************************************************/ + +#ifndef QMLJSDELTA_H +#define QMLJSDELTA_H + +#include "qmljsprivateapi.h" + +#include <qmljs/qmljsdocument.h> +#include <qmljs/parser/qmljsastfwd_p.h> + +namespace QmlJSInspector { +namespace Internal { + +class Delta +{ +public: + void operator()(QmlJS::Document::Ptr doc, QmlJS::Document::Ptr previousDoc); + +private: + void updateScriptBinding(const QDeclarativeDebugObjectReference &objectReference, + QmlJS::AST::UiScriptBinding *scriptBinding, + const QString &propertyName, + const QString &scriptCode); + + bool compare(QmlJS::AST::UiQualifiedId *id, QmlJS::AST::UiQualifiedId *other); + QmlJS::AST::UiObjectMemberList *objectMembers(QmlJS::AST::UiObjectMember *object); +}; + +} // namespace Internal +} // namespace QmlJSInspector + +#endif // QMLJSDELTA_H + diff --git a/src/plugins/qmljsinspector/qmljsinspector.pro b/src/plugins/qmljsinspector/qmljsinspector.pro index b1c337cc562cd8305c53573cf54f8136f9e7c8ba..d89b2fe6ac6973be553e6496bbe62beb9e4ccb66 100644 --- a/src/plugins/qmljsinspector/qmljsinspector.pro +++ b/src/plugins/qmljsinspector/qmljsinspector.pro @@ -16,14 +16,16 @@ qmljsinspectorconstants.h \ qmljsinspectorcontext.h \ qmljsinspectorplugin.h \ qmljsclientproxy.h \ -qmljsinspector.h +qmljsinspector.h \ +qmljsdelta.h SOURCES += \ qmljsdebuggerclient.cpp \ qmljsinspectorcontext.cpp \ qmljsinspectorplugin.cpp \ qmljsclientproxy.cpp \ -qmljsinspector.cpp +qmljsinspector.cpp \ +qmljsdelta.cpp OTHER_FILES += QmlJSInspector.pluginspec RESOURCES += qmljsinspector.qrc