QmlDesigner.Rewriter: Show error for wrong code model
[qt-creator:qt-creator.git] / src / plugins / qmldesigner / designercore / model / texttomodelmerger.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and Digia.  For licensing terms and
13 ** conditions see http://qt.digia.com/licensing.  For further information
14 ** use the contact form at http://qt.digia.com/contact-us.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Digia gives you certain additional
25 ** rights.  These rights are described in the Digia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ****************************************************************************/
29
30 #include "texttomodelmerger.h"
31 #include "abstractproperty.h"
32 #include "bindingproperty.h"
33 #include "filemanager/firstdefinitionfinder.h"
34 #include "filemanager/objectlengthcalculator.h"
35 #include "filemanager/qmlrefactoring.h"
36 #include "filemanager/qmlwarningdialog.h"
37 #include "nodeproperty.h"
38 #include "propertyparser.h"
39 #include "rewriterview.h"
40 #include "variantproperty.h"
41 #include "signalhandlerproperty.h"
42 #include "nodemetainfo.h"
43
44 #include <qmljs/qmljsevaluate.h>
45 #include <qmljs/qmljslink.h>
46 #include <qmljs/parser/qmljsast_p.h>
47 #include <qmljs/qmljscheck.h>
48 #include <qmljs/qmljsutils.h>
49 #include <qmljs/qmljsmodelmanagerinterface.h>
50 #include <qmljs/qmljsqrcparser.h>
51
52 #include <utils/qtcassert.h>
53
54 #include <QSet>
55 #include <QDir>
56
57 using namespace LanguageUtils;
58 using namespace QmlJS;
59 using namespace QmlJS::AST;
60
61 namespace {
62
63 static inline QStringList supportedVersionsList()
64 {
65     QStringList list;
66     list << QLatin1String("1.0") << QLatin1String("1.1") << QLatin1String("2.0") << QLatin1String("2.1") << QLatin1String("2.2");
67     return list;
68 }
69
70 static inline QStringList globalQtEnums()
71 {
72     static QStringList list = QStringList() << QLatin1String("Horizontal") << QLatin1String("Vertical") << QLatin1String("AlignVCenter")
73          << QLatin1String("AlignLeft") << QLatin1String("LeftToRight") << QLatin1String("RightToLeft");
74     return list;
75 }
76
77 static inline bool supportedQtQuickVersion(const QString &version)
78 {
79     static QStringList supportedVersions = supportedVersionsList();
80
81     return supportedVersions.contains(version);
82 }
83
84 static inline QString stripQuotes(const QString &str)
85 {
86     if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"')))
87             || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\''))))
88         return str.mid(1, str.length() - 2);
89
90     return str;
91 }
92
93 static inline QString deEscape(const QString &value)
94 {
95     QString result = value;
96
97     result.replace(QLatin1String("\\\\"), QLatin1String("\\"));
98     result.replace(QLatin1String("\\\""), QLatin1String("\""));
99     result.replace(QLatin1String("\\t"), QLatin1String("\t"));
100     result.replace(QLatin1String("\\r"), QLatin1String("\\\r"));
101     result.replace(QLatin1String("\\n"), QLatin1String("\n"));
102
103     return result;
104 }
105
106 static inline unsigned char convertHex(ushort c)
107 {
108     if (c >= '0' && c <= '9')
109         return (c - '0');
110     else if (c >= 'a' && c <= 'f')
111         return (c - 'a' + 10);
112     else
113         return (c - 'A' + 10);
114 }
115
116 QChar convertUnicode(ushort c1, ushort c2,
117                              ushort c3, ushort c4)
118 {
119     return QChar((convertHex(c3) << 4) + convertHex(c4),
120                   (convertHex(c1) << 4) + convertHex(c2));
121 }
122
123 static inline bool isHexDigit(ushort c)
124 {
125     return ((c >= '0' && c <= '9')
126             || (c >= 'a' && c <= 'f')
127             || (c >= 'A' && c <= 'F'));
128 }
129
130
131 static inline QString fixEscapedUnicodeChar(const QString &value) //convert "\u2939"
132 {
133     if (value.count() == 6 && value.at(0) == '\\' && value.at(1) == 'u' &&
134         isHexDigit(value.at(2).unicode()) && isHexDigit(value.at(3).unicode()) &&
135         isHexDigit(value.at(4).unicode()) && isHexDigit(value.at(5).unicode())) {
136             return convertUnicode(value.at(2).unicode(), value.at(3).unicode(), value.at(4).unicode(), value.at(5).unicode());
137     }
138     return value;
139 }
140
141 static inline bool isSignalPropertyName(const QString &signalName)
142 {
143     // see QmlCompiler::isSignalPropertyName
144     return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) &&
145            signalName.at(2).isLetter();
146 }
147
148 static inline QVariant cleverConvert(const QString &value)
149 {
150     if (value == "true")
151         return QVariant(true);
152     if (value == "false")
153         return QVariant(false);
154     bool flag;
155     int i = value.toInt(&flag);
156     if (flag)
157         return QVariant(i);
158     double d = value.toDouble(&flag);
159     if (flag)
160         return QVariant(d);
161     return QVariant(value);
162 }
163
164 static bool isLiteralValue(ExpressionNode *expr)
165 {
166     if (cast<NumericLiteral*>(expr))
167         return true;
168     else if (cast<StringLiteral*>(expr))
169         return true;
170     else if (UnaryPlusExpression *plusExpr = cast<UnaryPlusExpression*>(expr))
171         return isLiteralValue(plusExpr->expression);
172     else if (UnaryMinusExpression *minusExpr = cast<UnaryMinusExpression*>(expr))
173         return isLiteralValue(minusExpr->expression);
174     else if (cast<TrueLiteral*>(expr))
175         return true;
176     else if (cast<FalseLiteral*>(expr))
177         return true;
178     else
179         return false;
180 }
181
182 static bool isLiteralValue(Statement *stmt)
183 {
184     ExpressionStatement *exprStmt = cast<ExpressionStatement *>(stmt);
185     if (exprStmt)
186         return isLiteralValue(exprStmt->expression);
187     else
188         return false;
189 }
190
191 static inline bool isLiteralValue(UiScriptBinding *script)
192 {
193     if (!script || !script->statement)
194         return false;
195
196     return isLiteralValue(script->statement);
197 }
198
199 static inline int propertyType(const QString &typeName)
200 {
201     if (typeName == QLatin1String("bool"))
202         return QMetaType::type("bool");
203     else if (typeName == QLatin1String("color"))
204         return QMetaType::type("QColor");
205     else if (typeName == QLatin1String("date"))
206         return QMetaType::type("QDate");
207     else if (typeName == QLatin1String("int"))
208         return QMetaType::type("int");
209     else if (typeName == QLatin1String("real"))
210         return QMetaType::type("double");
211     else if (typeName == QLatin1String("double"))
212         return QMetaType::type("double");
213     else if (typeName == QLatin1String("string"))
214         return QMetaType::type("QString");
215     else if (typeName == QLatin1String("url"))
216         return QMetaType::type("QUrl");
217     else if (typeName == QLatin1String("var") || typeName == QLatin1String("variant"))
218         return QMetaType::type("QVariant");
219     else
220         return -1;
221 }
222
223 static inline QVariant convertDynamicPropertyValueToVariant(const QString &astValue,
224                                                             const QString &astType)
225 {
226     const QString cleanedValue = fixEscapedUnicodeChar(deEscape(stripQuotes(astValue.trimmed())));
227
228     if (astType.isEmpty())
229         return QString();
230
231     const int type = propertyType(astType);
232     if (type == QMetaType::type("QVariant")) {
233         if (cleanedValue.isNull()) // Explicitly isNull, NOT isEmpty!
234             return QVariant(static_cast<QVariant::Type>(type));
235         else
236             return QVariant(cleanedValue);
237     } else {
238         QVariant value = QVariant(cleanedValue);
239         value.convert(static_cast<QVariant::Type>(type));
240         return value;
241     }
242 }
243
244 static bool isComponentType(const QmlDesigner::TypeName &type)
245 {
246     return  type == "Component" || type == "Qt.Component" || type == "QtQuick.Component";
247 }
248
249 static bool isCustomParserType(const QString &type)
250 {
251     return type == "QtQuick.VisualItemModel" || type == "Qt.VisualItemModel" ||
252            type == "QtQuick.VisualDataModel" || type == "Qt.VisualDataModel" ||
253            type == "QtQuick.ListModel" || type == "Qt.ListModel" ||
254            type == "QtQuick.XmlListModel" || type == "Qt.XmlListModel";
255 }
256
257
258 static bool isPropertyChangesType(const QmlDesigner::TypeName &type)
259 {
260     return  type == "PropertyChanges" || type == "QtQuick.PropertyChanges" || type == "Qt.PropertyChanges";
261 }
262
263 static bool isConnectionsType(const QmlDesigner::TypeName &type)
264 {
265     return  type == "Connections" || type == "QtQuick.Connections" || type == "Qt.Connections";
266 }
267
268 static bool propertyIsComponentType(const QmlDesigner::NodeAbstractProperty &property, const QmlDesigner::TypeName &type, QmlDesigner::Model *model)
269 {
270     if (model->metaInfo(type, -1, -1).isSubclassOf("QtQuick.Component", -1, -1) && !isComponentType(type))
271         return false; //If the type is already a subclass of Component keep it
272
273     return property.parentModelNode().isValid() &&
274             isComponentType(property.parentModelNode().metaInfo().propertyTypeName(property.name()));
275 }
276
277 static inline QString extractComponentFromQml(const QString &source)
278 {
279     if (source.isEmpty())
280         return QString();
281
282     QString result;
283     if (source.contains("Component")) { //explicit component
284         QmlDesigner::FirstDefinitionFinder firstDefinitionFinder(source);
285         int offset = firstDefinitionFinder(0);
286         if (offset < 0)
287             return QString(); //No object definition found
288         QmlDesigner::ObjectLengthCalculator objectLengthCalculator;
289         unsigned length;
290         if (objectLengthCalculator(source, offset, length))
291             result = source.mid(offset, length);
292         else
293             result = source;
294     } else {
295         result = source; //implicit component
296     }
297     return result;
298 }
299
300 } // anonymous namespace
301
302 namespace QmlDesigner {
303 namespace Internal {
304
305 class ReadingContext
306 {
307 public:
308     ReadingContext(const Snapshot &snapshot, const Document::Ptr &doc,
309                    const QStringList importPaths)
310         : m_snapshot(snapshot)
311         , m_doc(doc)
312         , m_link(snapshot, importPaths,
313                  QmlJS::ModelManagerInterface::instance()->builtins(doc))
314         , m_context(m_link(doc, &m_diagnosticLinkMessages))
315         , m_scopeChain(doc, m_context)
316         , m_scopeBuilder(&m_scopeChain)
317     {
318     }
319
320     ~ReadingContext()
321     {}
322
323     Document::Ptr doc() const
324     { return m_doc; }
325
326     void enterScope(Node *node)
327     { m_scopeBuilder.push(node); }
328
329     void leaveScope()
330     { m_scopeBuilder.pop(); }
331
332     void lookup(UiQualifiedId *astTypeNode, QString &typeName, int &majorVersion,
333                 int &minorVersion, QString &defaultPropertyName)
334     {
335         const ObjectValue *value = m_context->lookupType(m_doc.data(), astTypeNode);
336         defaultPropertyName = m_context->defaultPropertyName(value);
337
338         const CppComponentValue * qmlValue = value_cast<CppComponentValue>(value);
339         if (qmlValue) {
340             typeName = qmlValue->moduleName() + QLatin1String(".") + qmlValue->className();
341
342             majorVersion = qmlValue->componentVersion().majorVersion();
343             minorVersion = qmlValue->componentVersion().minorVersion();
344         } else {
345             for (UiQualifiedId *iter = astTypeNode; iter; iter = iter->next)
346                 if (!iter->next && !iter->name.isEmpty())
347                     typeName = iter->name.toString();
348
349             QString fullTypeName;
350             for (UiQualifiedId *iter = astTypeNode; iter; iter = iter->next)
351                 if (!iter->name.isEmpty())
352                     fullTypeName += iter->name.toString() + QLatin1Char('.');
353
354             if (fullTypeName.endsWith(QLatin1Char('.')))
355                 fullTypeName.chop(1);
356
357             majorVersion = ComponentVersion::NoVersion;
358             minorVersion = ComponentVersion::NoVersion;
359
360             const Imports *imports = m_context->imports(m_doc.data());
361             ImportInfo importInfo = imports->info(fullTypeName, m_context.data());
362             if (importInfo.isValid() && importInfo.type() == ImportType::Library) {
363                 QString name = importInfo.name();
364                 majorVersion = importInfo.version().majorVersion();
365                 minorVersion = importInfo.version().minorVersion();
366                 typeName.prepend(name + QLatin1Char('.'));
367             } else if (importInfo.isValid() && importInfo.type() == ImportType::Directory) {
368                 QString path = importInfo.path();
369                 QDir dir(m_doc->path());
370                 // should probably try to make it relatve to some import path, not to the document path
371                 QString relativeDir = dir.relativeFilePath(path);
372                 QString name = relativeDir.replace(QLatin1Char('/'), QLatin1Char('.'));
373                 if (!name.isEmpty())
374                     typeName.prepend(name + QLatin1Char('.'));
375             } else if (importInfo.isValid() && importInfo.type() == ImportType::QrcDirectory) {
376                 QString path = QrcParser::normalizedQrcDirectoryPath(importInfo.path());
377                 path = path.mid(1, path.size() - ((path.size() > 1) ? 2 : 1));
378                 const QString name = path.replace(QLatin1Char('/'), QLatin1Char('.'));
379                 if (!name.isEmpty())
380                     typeName.prepend(name + QLatin1Char('.'));
381             }
382         }
383     }
384
385     /// When something is changed here, also change Check::checkScopeObjectMember in
386     /// qmljscheck.cpp
387     /// ### Maybe put this into the context as a helper function.
388     bool lookupProperty(const QString &prefix, const UiQualifiedId *id, const Value **property = 0, const ObjectValue **parentObject = 0, QString *name = 0)
389     {
390         QList<const ObjectValue *> scopeObjects = m_scopeChain.qmlScopeObjects();
391         if (scopeObjects.isEmpty())
392             return false;
393
394         if (! id)
395             return false; // ### error?
396
397         if (id->name.isEmpty()) // possible after error recovery
398             return false;
399
400         QString propertyName;
401         if (prefix.isEmpty())
402             propertyName = id->name.toString();
403         else
404             propertyName = prefix;
405
406         if (name)
407             *name = propertyName;
408
409         if (propertyName == QLatin1String("id") && ! id->next)
410             return false; // ### should probably be a special value
411
412         // attached properties
413         bool isAttachedProperty = false;
414         if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
415             isAttachedProperty = true;
416             if (const ObjectValue *qmlTypes = m_scopeChain.qmlTypes())
417                 scopeObjects += qmlTypes;
418         }
419
420         if (scopeObjects.isEmpty())
421             return false;
422
423         // global lookup for first part of id
424         const ObjectValue *objectValue = 0;
425         const Value *value = 0;
426         for (int i = scopeObjects.size() - 1; i >= 0; --i) {
427             objectValue = scopeObjects[i];
428             value = objectValue->lookupMember(propertyName, m_context);
429             if (value)
430                 break;
431         }
432         if (parentObject)
433             *parentObject = objectValue;
434         if (!value) {
435             qWarning() << "Skipping invalid property name" << propertyName;
436             return false;
437         }
438
439         // can't look up members for attached properties
440         if (isAttachedProperty)
441             return false;
442
443         // resolve references
444         if (const Reference *ref = value->asReference())
445             value = m_context->lookupReference(ref);
446
447         // member lookup
448         const UiQualifiedId *idPart = id;
449         if (prefix.isEmpty())
450             idPart = idPart->next;
451         for (; idPart; idPart = idPart->next) {
452             objectValue = value_cast<ObjectValue>(value);
453             if (! objectValue) {
454 //                if (idPart->name)
455 //                    qDebug() << idPart->name->asString() << "has no property named"
456 //                             << propertyName;
457                 return false;
458             }
459             if (parentObject)
460                 *parentObject = objectValue;
461
462             if (idPart->name.isEmpty()) {
463                 // somebody typed "id." and error recovery still gave us a valid tree,
464                 // so just bail out here.
465                 return false;
466             }
467
468             propertyName = idPart->name.toString();
469             if (name)
470                 *name = propertyName;
471
472             value = objectValue->lookupMember(propertyName, m_context);
473             if (! value) {
474 //                if (idPart->name)
475 //                    qDebug() << "In" << idPart->name->asString() << ":"
476 //                             << objectValue->className() << "has no property named"
477 //                             << propertyName;
478                 return false;
479             }
480         }
481
482         if (property)
483             *property = value;
484         return true;
485     }
486
487     bool isArrayProperty(const Value *value, const ObjectValue *containingObject, const QString &name)
488     {
489         if (!value)
490             return false;
491         const ObjectValue *objectValue = value->asObjectValue();
492         if (objectValue && objectValue->prototype(m_context) == m_context->valueOwner()->arrayPrototype())
493             return true;
494
495         PrototypeIterator iter(containingObject, m_context);
496         while (iter.hasNext()) {
497             const ObjectValue *proto = iter.next();
498             if (proto->lookupMember(name, m_context) == m_context->valueOwner()->arrayPrototype())
499                 return true;
500             if (const CppComponentValue *qmlIter = value_cast<CppComponentValue>(proto)) {
501                 if (qmlIter->isListProperty(name))
502                     return true;
503             }
504         }
505         return false;
506     }
507
508     QVariant convertToVariant(const QString &astValue, const QString &propertyPrefix, UiQualifiedId *propertyId)
509     {
510         const bool hasQuotes = astValue.trimmed().left(1) == QLatin1String("\"") && astValue.trimmed().right(1) == QLatin1String("\"");
511         const QString cleanedValue = fixEscapedUnicodeChar(deEscape(stripQuotes(astValue.trimmed())));
512         const Value *property = 0;
513         const ObjectValue *containingObject = 0;
514         QString name;
515         if (!lookupProperty(propertyPrefix, propertyId, &property, &containingObject, &name)) {
516             qWarning() << "Unknown property" << propertyPrefix + QLatin1Char('.') + toString(propertyId)
517                        << "on line" << propertyId->identifierToken.startLine
518                        << "column" << propertyId->identifierToken.startColumn;
519             return hasQuotes ? QVariant(cleanedValue) : cleverConvert(cleanedValue);
520         }
521
522         if (containingObject)
523             containingObject->lookupMember(name, m_context, &containingObject);
524
525         if (const CppComponentValue * qmlObject = value_cast<CppComponentValue>(containingObject)) {
526             const QString typeName = qmlObject->propertyType(name);
527             if (qmlObject->getEnum(typeName).isValid()) {
528                 return QVariant(cleanedValue);
529             } else {
530                 int type = QMetaType::type(typeName.toUtf8().constData());
531                 QVariant result;
532                 if (type)
533                     result = PropertyParser::read(type, cleanedValue);
534                 if (result.isValid())
535                     return result;
536             }
537         }
538
539         if (property->asColorValue())
540             return PropertyParser::read(QVariant::Color, cleanedValue);
541         else if (property->asUrlValue())
542             return PropertyParser::read(QVariant::Url, cleanedValue);
543
544         QVariant value(cleanedValue);
545         if (property->asBooleanValue()) {
546             value.convert(QVariant::Bool);
547             return value;
548         } else if (property->asNumberValue()) {
549             value.convert(QVariant::Double);
550             return value;
551         } else if (property->asStringValue()) {
552             // nothing to do
553         } else { //property alias et al
554             if (!hasQuotes)
555                 return cleverConvert(cleanedValue);
556         }
557         return value;
558     }
559
560     QVariant convertToEnum(Statement *rhs, const QString &propertyPrefix, UiQualifiedId *propertyId, const QString &astValue)
561     {
562         QStringList astValueList = astValue.split(QLatin1String("."));
563
564         if (astValueList.count() == 2 //Check for global Qt enums
565                 && astValueList.first() == QLatin1String("Qt")
566                 && globalQtEnums().contains(astValueList.last()))
567             return QVariant(astValueList.last());
568
569         ExpressionStatement *eStmt = cast<ExpressionStatement *>(rhs);
570         if (!eStmt || !eStmt->expression)
571             return QVariant();
572
573         const ObjectValue *containingObject = 0;
574         QString name;
575         if (!lookupProperty(propertyPrefix, propertyId, 0, &containingObject, &name))
576             return QVariant();
577
578         if (containingObject)
579             containingObject->lookupMember(name, m_context, &containingObject);
580         const CppComponentValue * lhsCppComponent = value_cast<CppComponentValue>(containingObject);
581         if (!lhsCppComponent)
582             return QVariant();
583         const QString lhsPropertyTypeName = lhsCppComponent->propertyType(name);
584
585         const ObjectValue *rhsValueObject = 0;
586         QString rhsValueName;
587         if (IdentifierExpression *idExp = cast<IdentifierExpression *>(eStmt->expression)) {
588             if (!m_scopeChain.qmlScopeObjects().isEmpty())
589                 rhsValueObject = m_scopeChain.qmlScopeObjects().last();
590             if (!idExp->name.isEmpty())
591                 rhsValueName = idExp->name.toString();
592         } else if (FieldMemberExpression *memberExp = cast<FieldMemberExpression *>(eStmt->expression)) {
593             Evaluate evaluate(&m_scopeChain);
594             const Value *result = evaluate(memberExp->base);
595             rhsValueObject = result->asObjectValue();
596
597             if (!memberExp->name.isEmpty())
598                 rhsValueName = memberExp->name.toString();
599         }
600
601         if (rhsValueObject)
602             rhsValueObject->lookupMember(rhsValueName, m_context, &rhsValueObject);
603
604         const CppComponentValue *rhsCppComponentValue = value_cast<CppComponentValue>(rhsValueObject);
605         if (!rhsCppComponentValue)
606             return QVariant();
607
608         if (rhsCppComponentValue->getEnum(lhsPropertyTypeName).hasKey(rhsValueName))
609             return QVariant(rhsValueName);
610         else
611             return QVariant();
612     }
613
614
615     const ScopeChain &scopeChain() const
616     { return m_scopeChain; }
617
618     QList<DiagnosticMessage> diagnosticLinkMessages() const
619     { return m_diagnosticLinkMessages; }
620
621 private:
622     Snapshot m_snapshot;
623     Document::Ptr m_doc;
624     Link m_link;
625     QList<DiagnosticMessage> m_diagnosticLinkMessages;
626     ContextPtr m_context;
627     ScopeChain m_scopeChain;
628     ScopeBuilder m_scopeBuilder;
629 };
630
631 } // namespace Internal
632 } // namespace QmlDesigner
633
634 using namespace QmlDesigner;
635 using namespace QmlDesigner::Internal;
636
637
638 static inline bool smartVeryFuzzyCompare(QVariant value1, QVariant value2)
639 { //we ignore slight changes on doubles and only check three digits
640     if ((value1.type() == QVariant::Double) || (value2.type() == QVariant::Double)) {
641         bool ok1, ok2;
642         qreal a = value1.toDouble(&ok1);
643         qreal b = value2.toDouble(&ok2);
644
645         if (!ok1 || !ok2)
646             return false;
647
648         if (qFuzzyCompare(a, b))
649             return true;
650
651         int ai = qRound(a * 1000);
652         int bi = qRound(b * 1000);
653
654         if (qFuzzyCompare((qreal(ai) / 1000), (qreal(bi) / 1000)))
655             return true;
656     }
657     return false;
658 }
659
660 static inline bool equals(const QVariant &a, const QVariant &b)
661 {
662     if (a == b)
663         return true;
664     if (smartVeryFuzzyCompare(a, b))
665         return true;
666     return false;
667 }
668
669 TextToModelMerger::TextToModelMerger(RewriterView *reWriterView) :
670         m_rewriterView(reWriterView),
671         m_isActive(false)
672 {
673     Q_ASSERT(reWriterView);
674     m_setupTimer.setSingleShot(true);
675     RewriterView::connect(&m_setupTimer, SIGNAL(timeout()), reWriterView, SLOT(delayedSetup()));
676 }
677
678 void TextToModelMerger::setActive(bool active)
679 {
680     m_isActive = active;
681 }
682
683 bool TextToModelMerger::isActive() const
684 {
685     return m_isActive;
686 }
687
688 void TextToModelMerger::setupImports(const Document::Ptr &doc,
689                                      DifferenceHandler &differenceHandler)
690 {
691     QList<Import> existingImports = m_rewriterView->model()->imports();
692
693     for (UiHeaderItemList *iter = doc->qmlProgram()->headers; iter; iter = iter->next) {
694         UiImport *import = AST::cast<UiImport *>(iter->headerItem);
695         if (!import)
696             continue;
697
698         QString version;
699         if (import->versionToken.isValid())
700             version = textAt(doc, import->versionToken);
701         const QString &as = import->importId.toString();
702
703         if (!import->fileName.isEmpty()) {
704             const QString strippedFileName = stripQuotes(import->fileName.toString());
705             const Import newImport = Import::createFileImport(strippedFileName,
706                                                               version, as, m_rewriterView->textModifier()->importPaths());
707
708             if (!existingImports.removeOne(newImport))
709                 differenceHandler.modelMissesImport(newImport);
710         } else {
711             QString importUri = toString(import->importUri);
712             if (importUri == QLatin1String("Qt") && version == QLatin1String("4.7")) {
713                 importUri = QLatin1String("QtQuick");
714                 version = QLatin1String("1.0");
715             }
716
717             const Import newImport =
718                     Import::createLibraryImport(importUri, version, as, m_rewriterView->textModifier()->importPaths());
719
720             if (!existingImports.removeOne(newImport))
721                 differenceHandler.modelMissesImport(newImport);
722         }
723     }
724
725     foreach (const Import &import, existingImports)
726         differenceHandler.importAbsentInQMl(import);
727 }
728
729 bool TextToModelMerger::load(const QString &data, DifferenceHandler &differenceHandler)
730 {
731 //    qDebug() << "TextToModelMerger::load with data:" << data;
732
733     const QUrl url = m_rewriterView->model()->fileUrl();
734     const QStringList importPaths = m_rewriterView->textModifier()->importPaths();
735     setActive(true);
736
737
738     try {
739         Snapshot snapshot = m_rewriterView->textModifier()->getSnapshot();
740         const QString fileName = url.toLocalFile();
741         Document::MutablePtr doc = Document::create(fileName.isEmpty() ? QLatin1String("<internal>") : fileName, Language::Qml);
742         doc->setSource(data);
743         doc->parseQml();
744
745         if (!doc->isParsedCorrectly()) {
746             QList<RewriterView::Error> errors;
747             foreach (const QmlJS::DiagnosticMessage &message, doc->diagnosticMessages())
748                 errors.append(RewriterView::Error(message, QUrl::fromLocalFile(doc->fileName())));
749             m_rewriterView->setErrors(errors);
750             setActive(false);
751             return false;
752         }
753         snapshot.insert(doc);
754         ReadingContext ctxt(snapshot, doc, importPaths);
755         m_scopeChain = QSharedPointer<const ScopeChain>(
756                     new ScopeChain(ctxt.scopeChain()));
757         m_document = doc;
758
759         QList<RewriterView::Error> errors;
760         QList<RewriterView::Error> warnings;
761
762         foreach (const QmlJS::DiagnosticMessage &diagnosticMessage, ctxt.diagnosticLinkMessages()) {
763             errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName())));
764         }
765
766         setupImports(doc, differenceHandler);
767
768         if (m_rewriterView->model()->imports().isEmpty()) {
769             const QmlJS::DiagnosticMessage diagnosticMessage(QmlJS::Severity::Error, AST::SourceLocation(0, 0, 0, 0), QCoreApplication::translate("QmlDesigner::TextToModelMerger", "No import statements found"));
770             errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName())));
771         }
772
773         foreach (const QmlDesigner::Import &import, m_rewriterView->model()->imports()) {
774             if (import.isLibraryImport() && import.url() == QLatin1String("QtQuick") && !supportedQtQuickVersion(import.version())) {
775                 const QmlJS::DiagnosticMessage diagnosticMessage(QmlJS::Severity::Error, AST::SourceLocation(0, 0, 0, 0),
776                                                                  QCoreApplication::translate("QmlDesigner::TextToModelMerger", "Unsupported QtQuick version"));
777                 errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName())));
778             }
779         }
780
781         if (view()->checkSemanticErrors()) {
782             Check check(doc, m_scopeChain->context());
783             check.disableMessage(StaticAnalysis::ErrUnknownComponent);
784             check.disableMessage(StaticAnalysis::ErrPrototypeCycle);
785             check.disableMessage(StaticAnalysis::ErrCouldNotResolvePrototype);
786             check.disableMessage(StaticAnalysis::ErrCouldNotResolvePrototypeOf);
787
788             foreach (StaticAnalysis::Type type, StaticAnalysis::Message::allMessageTypes()) {
789                 StaticAnalysis::PrototypeMessageData prototypeMessageData = StaticAnalysis::Message::prototypeForMessageType(type);
790                 if (prototypeMessageData.severity == Severity::MaybeWarning
791                         || prototypeMessageData.severity == Severity::Warning) {
792                     check.disableMessage(type);
793                 }
794             }
795
796             check.enableMessage(StaticAnalysis::WarnImperativeCodeNotEditableInVisualDesigner);
797             check.enableMessage(StaticAnalysis::WarnUnsupportedTypeInVisualDesigner);
798             check.enableMessage(StaticAnalysis::WarnReferenceToParentItemNotSupportedByVisualDesigner);
799             check.enableMessage(StaticAnalysis::WarnReferenceToParentItemNotSupportedByVisualDesigner);
800             check.enableMessage(StaticAnalysis::WarnAboutQtQuick1InsteadQtQuick2);
801             //## triggers too often ## check.enableMessage(StaticAnalysis::WarnUndefinedValueForVisualDesigner);
802
803             foreach (const StaticAnalysis::Message &message, check()) {
804                 if (message.severity == Severity::Error)
805                     errors.append(RewriterView::Error(message.toDiagnosticMessage(), QUrl::fromLocalFile(doc->fileName())));
806                 if (message.severity == Severity::Warning) {
807                     if (message.type == StaticAnalysis::WarnAboutQtQuick1InsteadQtQuick2) {
808                         errors.append(RewriterView::Error(message.toDiagnosticMessage(), QUrl::fromLocalFile(doc->fileName())));
809                     } else {
810                         warnings.append(RewriterView::Error(message.toDiagnosticMessage(), QUrl::fromLocalFile(doc->fileName())));
811                     }
812                 }
813             }
814
815             if (!errors.isEmpty()) {
816                 m_rewriterView->setErrors(errors);
817                 setActive(false);
818                 return false;
819             }
820
821             if (!warnings.isEmpty() && differenceHandler.isValidator() && !m_rewriterView->inErrorState()) {
822
823                 QString title = QCoreApplication::translate("QmlDesigner::TextToModelMerger", "This .qml file contains features "
824                                                             "which are not supported by Qt Quick Designer");
825
826                 QStringList message;
827
828                 foreach (const RewriterView::Error &warning, warnings) {
829                     QString string = QLatin1String("Line: ") +  QString::number(warning.line()) + QLatin1String(": ")  + warning.description();
830                     //string += QLatin1String(" <a href=\"") + QString::number(warning.line()) + QLatin1String("\">Go to error</a>") + QLatin1String("<p>");
831                     message << string;
832                 }
833
834                 QmlWarningDialog warningDialog(0, message);
835                 if (warningDialog.warningsEnabled() && warningDialog.exec()) {
836                     m_rewriterView->setErrors(warnings);
837                     setActive(false);
838                     return false;
839                 }
840             }
841         }
842
843         UiObjectMember *astRootNode = 0;
844         if (UiProgram *program = doc->qmlProgram())
845             if (program->members)
846                 astRootNode = program->members->member;
847         ModelNode modelRootNode = m_rewriterView->rootModelNode();
848         syncNode(modelRootNode, astRootNode, &ctxt, differenceHandler);
849         m_rewriterView->positionStorage()->cleanupInvalidOffsets();
850         m_rewriterView->clearErrors();
851
852         setActive(false);
853         return true;
854     } catch (Exception &e) {
855         RewriterView::Error error(&e);
856         // Somehow, the error below gets eaten in upper levels, so printing the
857         // exception info here for debugging purposes:
858         qDebug() << "*** An exception occurred while reading the QML file:"
859                  << error.toString();
860         m_rewriterView->addError(error);
861
862         setActive(false);
863
864         return false;
865     }
866 }
867
868 void TextToModelMerger::syncNode(ModelNode &modelNode,
869                                  UiObjectMember *astNode,
870                                  ReadingContext *context,
871                                  DifferenceHandler &differenceHandler)
872 {
873     UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
874     UiObjectInitializer *astInitializer = initializerOfObject(astNode);
875
876     if (!astObjectType || !astInitializer)
877         return;
878
879     m_rewriterView->positionStorage()->setNodeOffset(modelNode, astObjectType->identifierToken.offset);
880
881     QString typeNameString;
882     QString defaultPropertyNameString;
883     int majorVersion;
884     int minorVersion;
885     context->lookup(astObjectType, typeNameString, majorVersion, minorVersion, defaultPropertyNameString);
886
887     TypeName typeName = typeNameString.toUtf8();
888     PropertyName defaultPropertyName = defaultPropertyNameString.toUtf8();
889
890     if (defaultPropertyName.isEmpty()) //fallback and use the meta system of the model
891         defaultPropertyName = modelNode.metaInfo().defaultPropertyName();
892
893     if (typeName.isEmpty()) {
894         qWarning() << "Skipping node with unknown type" << toString(astObjectType);
895         return;
896     }
897
898     if (modelNode.isRootNode() && isComponentType(typeName)) {
899         for (UiObjectMemberList *iter = astInitializer->members; iter; iter = iter->next) {
900             if (UiObjectDefinition *def = cast<UiObjectDefinition *>(iter->member)) {
901                 syncNode(modelNode, def, context, differenceHandler);
902                 return;
903             }
904         }
905     }
906
907     bool isImplicitComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
908
909
910     if (modelNode.type() != typeName //If there is no valid parentProperty                                                                                                      //the node has just been created. The type is correct then.
911             || modelNode.majorVersion() != majorVersion
912             || modelNode.minorVersion() != minorVersion) {
913         const bool isRootNode = m_rewriterView->rootModelNode() == modelNode;
914         differenceHandler.typeDiffers(isRootNode, modelNode, typeName,
915                                       majorVersion, minorVersion,
916                                       astNode, context);
917         if (!isRootNode)
918             return; // the difference handler will create a new node, so we're done.
919     }
920
921     if (isComponentType(typeName) || isImplicitComponent)
922         setupComponentDelayed(modelNode, differenceHandler.isValidator());
923
924     if (isCustomParserType(typeName))
925         setupCustomParserNodeDelayed(modelNode, differenceHandler.isValidator());
926
927     context->enterScope(astNode);
928
929     QSet<PropertyName> modelPropertyNames = QSet<PropertyName>::fromList(modelNode.propertyNames());
930     if (!modelNode.id().isEmpty())
931         modelPropertyNames.insert("id");
932     QList<UiObjectMember *> defaultPropertyItems;
933
934     for (UiObjectMemberList *iter = astInitializer->members; iter; iter = iter->next) {
935         UiObjectMember *member = iter->member;
936         if (!member)
937             continue;
938
939         if (UiArrayBinding *array = cast<UiArrayBinding *>(member)) {
940             const QString astPropertyName = toString(array->qualifiedId);
941             if (isPropertyChangesType(typeName) || isConnectionsType(typeName) || context->lookupProperty(QString(), array->qualifiedId)) {
942                 AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
943                 QList<UiObjectMember *> arrayMembers;
944                 for (UiArrayMemberList *iter = array->members; iter; iter = iter->next)
945                     if (UiObjectMember *member = iter->member)
946                         arrayMembers.append(member);
947
948                 syncArrayProperty(modelProperty, arrayMembers, context, differenceHandler);
949                 modelPropertyNames.remove(astPropertyName.toUtf8());
950             } else {
951                 qWarning() << "Skipping invalid array property" << astPropertyName
952                            << "for node type" << modelNode.type();
953             }
954         } else if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) {
955             const QString &name = def->qualifiedTypeNameId->name.toString();
956             if (name.isEmpty() || !name.at(0).isUpper()) {
957                 QStringList props = syncGroupedProperties(modelNode,
958                                                           name,
959                                                           def->initializer->members,
960                                                           context,
961                                                           differenceHandler);
962                 foreach (const QString &prop, props)
963                     modelPropertyNames.remove(prop.toUtf8());
964             } else {
965                 defaultPropertyItems.append(member);
966             }
967         } else if (UiObjectBinding *binding = cast<UiObjectBinding *>(member)) {
968             const QString astPropertyName = toString(binding->qualifiedId);
969             if (binding->hasOnToken) {
970                 // skip value sources
971             } else {
972                 const Value *propertyType = 0;
973                 const ObjectValue *containingObject = 0;
974                 QString name;
975                 if (context->lookupProperty(QString(), binding->qualifiedId, &propertyType, &containingObject, &name)
976                         || isPropertyChangesType(typeName)
977                         || isConnectionsType(typeName)) {
978                     AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
979                     if (context->isArrayProperty(propertyType, containingObject, name))
980                         syncArrayProperty(modelProperty, QList<QmlJS::AST::UiObjectMember*>() << member, context, differenceHandler);
981                     else
982                         syncNodeProperty(modelProperty, binding, context, differenceHandler);
983                     modelPropertyNames.remove(astPropertyName.toUtf8());
984                 } else {
985                     qWarning() << "Skipping invalid node property" << astPropertyName
986                                << "for node type" << modelNode.type();
987                 }
988             }
989         } else if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) {
990             modelPropertyNames.remove(syncScriptBinding(modelNode, QString(), script, context, differenceHandler));
991         } else if (UiPublicMember *property = cast<UiPublicMember *>(member)) {
992             if (property->type == UiPublicMember::Signal)
993                 continue; // QML designer doesn't support this yet.
994
995             if (property->name.isEmpty() || property->memberType.isEmpty())
996                 continue; // better safe than sorry.
997
998             const QStringRef astName = property->name;
999             QString astValue;
1000             if (property->statement)
1001                 astValue = textAt(context->doc(),
1002                                   property->statement->firstSourceLocation(),
1003                                   property->statement->lastSourceLocation());
1004             const TypeName &astType = property->memberType.toUtf8();
1005             AbstractProperty modelProperty = modelNode.property(astName.toUtf8());
1006             if (!property->statement || isLiteralValue(property->statement)) {
1007                 const QVariant variantValue = convertDynamicPropertyValueToVariant(astValue, astType);
1008                 syncVariantProperty(modelProperty, variantValue, astType, differenceHandler);
1009             } else {
1010                 syncExpressionProperty(modelProperty, astValue, astType, differenceHandler);
1011             }
1012             modelPropertyNames.remove(astName.toUtf8());
1013         } else {
1014             qWarning() << "Found an unknown QML value.";
1015         }
1016     }
1017
1018     if (!defaultPropertyItems.isEmpty()) {
1019         if (isComponentType(modelNode.type()))
1020             setupComponentDelayed(modelNode, differenceHandler.isValidator());
1021         if (defaultPropertyName.isEmpty()) {
1022             qWarning() << "No default property for node type" << modelNode.type() << ", ignoring child items.";
1023         } else {
1024             AbstractProperty modelProperty = modelNode.property(defaultPropertyName);
1025             if (modelProperty.isNodeListProperty()) {
1026                 NodeListProperty nodeListProperty = modelProperty.toNodeListProperty();
1027                 syncNodeListProperty(nodeListProperty, defaultPropertyItems, context,
1028                                      differenceHandler);
1029             } else {
1030                 differenceHandler.shouldBeNodeListProperty(modelProperty,
1031                                                            defaultPropertyItems,
1032                                                            context);
1033             }
1034             modelPropertyNames.remove(defaultPropertyName);
1035         }
1036     }
1037
1038     foreach (const PropertyName &modelPropertyName, modelPropertyNames) {
1039         AbstractProperty modelProperty = modelNode.property(modelPropertyName);
1040
1041         // property deleted.
1042         if (modelPropertyName == "id")
1043             differenceHandler.idsDiffer(modelNode, QString());
1044         else
1045             differenceHandler.propertyAbsentFromQml(modelProperty);
1046     }
1047
1048     context->leaveScope();
1049 }
1050
1051 static QVariant parsePropertyExpression(ExpressionNode *expressionNode)
1052 {
1053     Q_ASSERT(expressionNode);
1054
1055     ArrayLiteral *arrayLiteral = cast<ArrayLiteral *>(expressionNode);
1056
1057     if (arrayLiteral) {
1058         QList<QVariant> variantList;
1059         for (ElementList *it = arrayLiteral->elements; it; it = it->next)
1060             variantList << parsePropertyExpression(it->expression);
1061         return variantList;
1062     }
1063
1064     StringLiteral *stringLiteral = cast<AST::StringLiteral *>(expressionNode);
1065     if (stringLiteral)
1066         return stringLiteral->value.toString();
1067
1068     TrueLiteral *trueLiteral = cast<AST::TrueLiteral *>(expressionNode);
1069     if (trueLiteral)
1070         return true;
1071
1072     FalseLiteral *falseLiteral = cast<AST::FalseLiteral *>(expressionNode);
1073     if (falseLiteral)
1074         return false;
1075
1076     NumericLiteral *numericLiteral = cast<AST::NumericLiteral *>(expressionNode);
1077     if (numericLiteral)
1078         return numericLiteral->value;
1079
1080
1081     return QVariant();
1082 }
1083
1084 QVariant parsePropertyScriptBinding(UiScriptBinding *uiScriptBinding)
1085 {
1086     Q_ASSERT(uiScriptBinding);
1087
1088     ExpressionStatement *expStmt = cast<ExpressionStatement *>(uiScriptBinding->statement);
1089     if (!expStmt)
1090         return QVariant();
1091
1092     return parsePropertyExpression(expStmt->expression);
1093 }
1094
1095 QmlDesigner::PropertyName TextToModelMerger::syncScriptBinding(ModelNode &modelNode,
1096                                              const QString &prefix,
1097                                              UiScriptBinding *script,
1098                                              ReadingContext *context,
1099                                              DifferenceHandler &differenceHandler)
1100 {
1101     QString astPropertyName = toString(script->qualifiedId);
1102     if (!prefix.isEmpty())
1103         astPropertyName.prepend(prefix + QLatin1Char('.'));
1104
1105     QString astValue;
1106     if (script->statement) {
1107         astValue = textAt(context->doc(),
1108                           script->statement->firstSourceLocation(),
1109                           script->statement->lastSourceLocation());
1110         astValue = astValue.trimmed();
1111         if (astValue.endsWith(QLatin1Char(';')))
1112             astValue = astValue.left(astValue.length() - 1);
1113         astValue = astValue.trimmed();
1114     }
1115
1116     if (astPropertyName == QLatin1String("id")) {
1117         syncNodeId(modelNode, astValue, differenceHandler);
1118         return astPropertyName.toUtf8();
1119     }
1120
1121     if (isSignalPropertyName(astPropertyName)) {
1122         AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
1123         syncSignalHandler(modelProperty, astValue, differenceHandler);
1124         return astPropertyName.toUtf8();
1125     }
1126
1127     if (isLiteralValue(script)) {
1128         if (isPropertyChangesType(modelNode.type())
1129                 || isConnectionsType(modelNode.type())) {
1130             AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
1131             QVariant variantValue = parsePropertyScriptBinding(script);
1132             if (!variantValue.isValid())
1133                 variantValue = deEscape(stripQuotes(astValue));
1134             syncVariantProperty(modelProperty, variantValue, TypeName(), differenceHandler);
1135             return astPropertyName.toUtf8();
1136         } else {
1137             const QVariant variantValue = context->convertToVariant(astValue, prefix, script->qualifiedId);
1138             if (variantValue.isValid()) {
1139                 AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
1140                 syncVariantProperty(modelProperty, variantValue, TypeName(), differenceHandler);
1141                 return astPropertyName.toUtf8();
1142             } else {
1143                 qWarning() << "Skipping invalid variant property" << astPropertyName
1144                            << "for node type" << modelNode.type();
1145                 return PropertyName();
1146             }
1147         }
1148     }
1149
1150     const QVariant enumValue = context->convertToEnum(script->statement, prefix, script->qualifiedId, astValue);
1151     if (enumValue.isValid()) { // It is a qualified enum:
1152         AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
1153         syncVariantProperty(modelProperty, enumValue, TypeName(), differenceHandler); // TODO: parse type
1154         return astPropertyName.toUtf8();
1155     } else { // Not an enum, so:
1156         if (isPropertyChangesType(modelNode.type())
1157                 || isConnectionsType(modelNode.type())
1158                 || context->lookupProperty(prefix, script->qualifiedId)) {
1159             AbstractProperty modelProperty = modelNode.property(astPropertyName.toUtf8());
1160             syncExpressionProperty(modelProperty, astValue, TypeName(), differenceHandler); // TODO: parse type
1161             return astPropertyName.toUtf8();
1162         } else {
1163             qWarning() << "Skipping invalid expression property" << astPropertyName
1164                     << "for node type" << modelNode.type();
1165             return PropertyName();
1166         }
1167     }
1168 }
1169
1170 void TextToModelMerger::syncNodeId(ModelNode &modelNode, const QString &astObjectId,
1171                                    DifferenceHandler &differenceHandler)
1172 {
1173     if (astObjectId.isEmpty()) {
1174         if (!modelNode.id().isEmpty()) {
1175             ModelNode existingNodeWithId = m_rewriterView->modelNodeForId(astObjectId);
1176             if (existingNodeWithId.isValid())
1177                 existingNodeWithId.setId(QString());
1178             differenceHandler.idsDiffer(modelNode, astObjectId);
1179         }
1180     } else {
1181         if (modelNode.id() != astObjectId) {
1182             ModelNode existingNodeWithId = m_rewriterView->modelNodeForId(astObjectId);
1183             if (existingNodeWithId.isValid())
1184                 existingNodeWithId.setId(QString());
1185             differenceHandler.idsDiffer(modelNode, astObjectId);
1186         }
1187     }
1188 }
1189
1190 void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty,
1191                                          UiObjectBinding *binding,
1192                                          ReadingContext *context,
1193                                          DifferenceHandler &differenceHandler)
1194 {
1195     QString typeNameString;
1196     QString dummy;
1197     int majorVersion;
1198     int minorVersion;
1199     context->lookup(binding->qualifiedTypeNameId, typeNameString, majorVersion, minorVersion, dummy);
1200
1201     TypeName typeName = typeNameString.toUtf8();
1202
1203     if (typeName.isEmpty()) {
1204         qWarning() << "Skipping node with unknown type" << toString(binding->qualifiedTypeNameId);
1205         return;
1206     }
1207
1208     if (modelProperty.isNodeProperty()) {
1209         ModelNode nodePropertyNode = modelProperty.toNodeProperty().modelNode();
1210         syncNode(nodePropertyNode, binding, context, differenceHandler);
1211     } else {
1212         differenceHandler.shouldBeNodeProperty(modelProperty,
1213                                                typeName,
1214                                                majorVersion,
1215                                                minorVersion,
1216                                                binding,
1217                                                context);
1218     }
1219 }
1220
1221 void TextToModelMerger::syncExpressionProperty(AbstractProperty &modelProperty,
1222                                                const QString &javascript,
1223                                                const TypeName &astType,
1224                                                DifferenceHandler &differenceHandler)
1225 {
1226     if (modelProperty.isBindingProperty()) {
1227         BindingProperty bindingProperty = modelProperty.toBindingProperty();
1228         if (bindingProperty.expression() != javascript
1229                 || !astType.isEmpty() != bindingProperty.isDynamic()
1230                 || astType != bindingProperty.dynamicTypeName()) {
1231             differenceHandler.bindingExpressionsDiffer(bindingProperty, javascript, astType);
1232         }
1233     } else {
1234         differenceHandler.shouldBeBindingProperty(modelProperty, javascript, astType);
1235     }
1236 }
1237
1238 void TextToModelMerger::syncSignalHandler(AbstractProperty &modelProperty,
1239                                                const QString &javascript,
1240                                                DifferenceHandler &differenceHandler)
1241 {
1242     if (modelProperty.isSignalHandlerProperty()) {
1243         SignalHandlerProperty signalHandlerProperty = modelProperty.toSignalHandlerProperty();
1244         if (signalHandlerProperty.source() != javascript)
1245             differenceHandler.signalHandlerSourceDiffer(signalHandlerProperty, javascript);
1246     } else {
1247         differenceHandler.shouldBeSignalHandlerProperty(modelProperty, javascript);
1248     }
1249 }
1250
1251
1252 void TextToModelMerger::syncArrayProperty(AbstractProperty &modelProperty,
1253                                           const QList<UiObjectMember *> &arrayMembers,
1254                                           ReadingContext *context,
1255                                           DifferenceHandler &differenceHandler)
1256 {
1257     if (modelProperty.isNodeListProperty()) {
1258         NodeListProperty nodeListProperty = modelProperty.toNodeListProperty();
1259         syncNodeListProperty(nodeListProperty, arrayMembers, context, differenceHandler);
1260     } else {
1261         differenceHandler.shouldBeNodeListProperty(modelProperty,
1262                                                    arrayMembers,
1263                                                    context);
1264     }
1265 }
1266
1267 void TextToModelMerger::syncVariantProperty(AbstractProperty &modelProperty,
1268                                             const QVariant &astValue,
1269                                             const TypeName &astType,
1270                                             DifferenceHandler &differenceHandler)
1271 {
1272     if (modelProperty.isVariantProperty()) {
1273         VariantProperty modelVariantProperty = modelProperty.toVariantProperty();
1274
1275         if (!equals(modelVariantProperty.value(), astValue)
1276                 || !astType.isEmpty() != modelVariantProperty.isDynamic()
1277                 || astType != modelVariantProperty.dynamicTypeName()) {
1278             differenceHandler.variantValuesDiffer(modelVariantProperty,
1279                                                   astValue,
1280                                                   astType);
1281         }
1282     } else {
1283         differenceHandler.shouldBeVariantProperty(modelProperty,
1284                                                   astValue,
1285                                                   astType);
1286     }
1287 }
1288
1289 void TextToModelMerger::syncNodeListProperty(NodeListProperty &modelListProperty,
1290                                              const QList<UiObjectMember *> arrayMembers,
1291                                              ReadingContext *context,
1292                                              DifferenceHandler &differenceHandler)
1293 {
1294     QList<ModelNode> modelNodes = modelListProperty.toModelNodeList();
1295     int i = 0;
1296     for (; i < modelNodes.size() && i < arrayMembers.size(); ++i) {
1297         ModelNode modelNode = modelNodes.at(i);
1298         syncNode(modelNode, arrayMembers.at(i), context, differenceHandler);
1299     }
1300
1301     for (int j = i; j < arrayMembers.size(); ++j) {
1302         // more elements in the dom-list, so add them to the model
1303         UiObjectMember *arrayMember = arrayMembers.at(j);
1304         const ModelNode newNode = differenceHandler.listPropertyMissingModelNode(modelListProperty, context, arrayMember);
1305     }
1306
1307     for (int j = i; j < modelNodes.size(); ++j) {
1308         // more elements in the model, so remove them.
1309         ModelNode modelNode = modelNodes.at(j);
1310         differenceHandler.modelNodeAbsentFromQml(modelNode);
1311     }
1312 }
1313
1314 ModelNode TextToModelMerger::createModelNode(const TypeName &typeName,
1315                                              int majorVersion,
1316                                              int minorVersion,
1317                                              bool isImplicitComponent,
1318                                              UiObjectMember *astNode,
1319                                              ReadingContext *context,
1320                                              DifferenceHandler &differenceHandler)
1321 {
1322     QString nodeSource;
1323
1324     UiQualifiedId *astObjectType = qualifiedTypeNameId(astNode);
1325
1326     if (isCustomParserType(typeName))
1327         nodeSource = textAt(context->doc(),
1328                                     astObjectType->identifierToken.offset,
1329                                     astNode->lastSourceLocation());
1330
1331
1332     if (isComponentType(typeName) || isImplicitComponent) {
1333         QString componentSource = extractComponentFromQml(textAt(context->doc(),
1334                                   astObjectType->identifierToken.offset,
1335                                   astNode->lastSourceLocation()));
1336
1337
1338         nodeSource = componentSource;
1339     }
1340
1341     ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource;
1342
1343     if (isComponentType(typeName) || isImplicitComponent)
1344         nodeSourceType = ModelNode::NodeWithComponentSource;
1345     else if (isCustomParserType(typeName))
1346         nodeSourceType = ModelNode::NodeWithCustomParserSource;
1347
1348     ModelNode newNode = m_rewriterView->createModelNode(typeName,
1349                                                         majorVersion,
1350                                                         minorVersion,
1351                                                         PropertyListType(),
1352                                                         PropertyListType(),
1353                                                         nodeSource,
1354                                                         nodeSourceType);
1355
1356     syncNode(newNode, astNode, context, differenceHandler);
1357     return newNode;
1358 }
1359
1360 QStringList TextToModelMerger::syncGroupedProperties(ModelNode &modelNode,
1361                                                      const QString &name,
1362                                                      UiObjectMemberList *members,
1363                                                      ReadingContext *context,
1364                                                      DifferenceHandler &differenceHandler)
1365 {
1366     QStringList props;
1367
1368     for (UiObjectMemberList *iter = members; iter; iter = iter->next) {
1369         UiObjectMember *member = iter->member;
1370
1371         if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) {
1372             const QString prop = syncScriptBinding(modelNode, name, script, context, differenceHandler);
1373             if (!prop.isEmpty())
1374                 props.append(prop);
1375         }
1376     }
1377
1378     return props;
1379 }
1380
1381 void ModelValidator::modelMissesImport(const QmlDesigner::Import &import)
1382 {
1383     Q_UNUSED(import)
1384     Q_ASSERT(m_merger->view()->model()->imports().contains(import));
1385 }
1386
1387 void ModelValidator::importAbsentInQMl(const QmlDesigner::Import &import)
1388 {
1389     Q_UNUSED(import)
1390     Q_ASSERT(! m_merger->view()->model()->imports().contains(import));
1391 }
1392
1393 void ModelValidator::bindingExpressionsDiffer(BindingProperty &modelProperty,
1394                                               const QString &javascript,
1395                                               const TypeName &astType)
1396 {
1397     Q_UNUSED(modelProperty)
1398     Q_UNUSED(javascript)
1399     Q_UNUSED(astType)
1400     Q_ASSERT(modelProperty.expression() == javascript);
1401     Q_ASSERT(modelProperty.dynamicTypeName() == astType);
1402     Q_ASSERT(0);
1403 }
1404
1405 void ModelValidator::shouldBeBindingProperty(AbstractProperty &modelProperty,
1406                                              const QString &/*javascript*/,
1407                                              const TypeName &/*astType*/)
1408 {
1409     Q_UNUSED(modelProperty)
1410     Q_ASSERT(modelProperty.isBindingProperty());
1411     Q_ASSERT(0);
1412 }
1413
1414 void ModelValidator::signalHandlerSourceDiffer(SignalHandlerProperty &modelProperty, const QString &javascript)
1415 {
1416     Q_UNUSED(modelProperty)
1417     Q_UNUSED(javascript)
1418     QTC_ASSERT(modelProperty.source() == javascript, return);
1419 }
1420
1421 void ModelValidator::shouldBeSignalHandlerProperty(AbstractProperty &modelProperty, const QString & /*javascript*/)
1422 {
1423     Q_UNUSED(modelProperty)
1424     Q_ASSERT(modelProperty.isSignalHandlerProperty());
1425     Q_ASSERT(0);
1426 }
1427
1428 void ModelValidator::shouldBeNodeListProperty(AbstractProperty &modelProperty,
1429                                               const QList<UiObjectMember *> /*arrayMembers*/,
1430                                               ReadingContext * /*context*/)
1431 {
1432     Q_UNUSED(modelProperty)
1433     Q_ASSERT(modelProperty.isNodeListProperty());
1434     Q_ASSERT(0);
1435 }
1436
1437 void ModelValidator::variantValuesDiffer(VariantProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName)
1438 {
1439     Q_UNUSED(modelProperty)
1440     Q_UNUSED(qmlVariantValue)
1441     Q_UNUSED(dynamicTypeName)
1442
1443     QTC_ASSERT(modelProperty.isDynamic() == !dynamicTypeName.isEmpty(), return);
1444     if (modelProperty.isDynamic()) {
1445         QTC_ASSERT(modelProperty.dynamicTypeName() == dynamicTypeName, return);
1446     }
1447
1448     QTC_ASSERT(equals(modelProperty.value(), qmlVariantValue), return);
1449     QTC_ASSERT(0, return);
1450 }
1451
1452 void ModelValidator::shouldBeVariantProperty(AbstractProperty &modelProperty, const QVariant &/*qmlVariantValue*/, const TypeName &/*dynamicTypeName*/)
1453 {
1454     Q_UNUSED(modelProperty)
1455
1456     Q_ASSERT(modelProperty.isVariantProperty());
1457     Q_ASSERT(0);
1458 }
1459
1460 void ModelValidator::shouldBeNodeProperty(AbstractProperty &modelProperty,
1461                                           const TypeName &/*typeName*/,
1462                                           int /*majorVersion*/,
1463                                           int /*minorVersion*/,
1464                                           UiObjectMember * /*astNode*/,
1465                                           ReadingContext * /*context*/)
1466 {
1467     Q_UNUSED(modelProperty)
1468
1469     Q_ASSERT(modelProperty.isNodeProperty());
1470     Q_ASSERT(0);
1471 }
1472
1473 void ModelValidator::modelNodeAbsentFromQml(ModelNode &modelNode)
1474 {
1475     Q_UNUSED(modelNode)
1476
1477     Q_ASSERT(!modelNode.isValid());
1478     Q_ASSERT(0);
1479 }
1480
1481 ModelNode ModelValidator::listPropertyMissingModelNode(NodeListProperty &/*modelProperty*/,
1482                                                        ReadingContext * /*context*/,
1483                                                        UiObjectMember * /*arrayMember*/)
1484 {
1485     Q_ASSERT(0);
1486     return ModelNode();
1487 }
1488
1489 void ModelValidator::typeDiffers(bool /*isRootNode*/,
1490                                  ModelNode &modelNode,
1491                                  const TypeName &typeName,
1492                                  int majorVersion,
1493                                  int minorVersion,
1494                                  QmlJS::AST::UiObjectMember * /*astNode*/,
1495                                  ReadingContext * /*context*/)
1496 {
1497     Q_UNUSED(modelNode)
1498     Q_UNUSED(typeName)
1499     Q_UNUSED(minorVersion)
1500     Q_UNUSED(majorVersion)
1501
1502     QTC_ASSERT(modelNode.type() == typeName, return);
1503     QTC_ASSERT(modelNode.majorVersion() == majorVersion, return);
1504     QTC_ASSERT(modelNode.minorVersion() == minorVersion, return);
1505     QTC_ASSERT(0, return);
1506 }
1507
1508 void ModelValidator::propertyAbsentFromQml(AbstractProperty &modelProperty)
1509 {
1510     Q_UNUSED(modelProperty)
1511
1512     Q_ASSERT(!modelProperty.isValid());
1513     Q_ASSERT(0);
1514 }
1515
1516 void ModelValidator::idsDiffer(ModelNode &modelNode, const QString &qmlId)
1517 {
1518     Q_UNUSED(modelNode)
1519     Q_UNUSED(qmlId)
1520
1521     QTC_ASSERT(modelNode.id() == qmlId, return);
1522     QTC_ASSERT(0, return);
1523 }
1524
1525 void ModelAmender::modelMissesImport(const QmlDesigner::Import &import)
1526 {
1527     m_merger->view()->model()->changeImports(QList<QmlDesigner::Import>() << import, QList<QmlDesigner::Import>());
1528 }
1529
1530 void ModelAmender::importAbsentInQMl(const QmlDesigner::Import &import)
1531 {
1532     m_merger->view()->model()->changeImports(QList<Import>(), QList<Import>() << import);
1533 }
1534
1535 void ModelAmender::bindingExpressionsDiffer(BindingProperty &modelProperty,
1536                                             const QString &javascript,
1537                                             const TypeName &astType)
1538 {
1539     if (astType.isEmpty())
1540         modelProperty.setExpression(javascript);
1541     else
1542         modelProperty.setDynamicTypeNameAndExpression(astType, javascript);
1543 }
1544
1545 void ModelAmender::shouldBeBindingProperty(AbstractProperty &modelProperty,
1546                                            const QString &javascript,
1547                                            const TypeName &astType)
1548 {
1549     ModelNode theNode = modelProperty.parentModelNode();
1550     BindingProperty newModelProperty = theNode.bindingProperty(modelProperty.name());
1551     if (astType.isEmpty())
1552         newModelProperty.setExpression(javascript);
1553     else
1554         newModelProperty.setDynamicTypeNameAndExpression(astType, javascript);
1555 }
1556
1557 void ModelAmender::signalHandlerSourceDiffer(SignalHandlerProperty &modelProperty, const QString &javascript)
1558 {
1559     modelProperty.setSource(javascript);
1560 }
1561
1562 void ModelAmender::shouldBeSignalHandlerProperty(AbstractProperty &modelProperty, const QString &javascript)
1563 {
1564     ModelNode theNode = modelProperty.parentModelNode();
1565     SignalHandlerProperty newModelProperty = theNode.signalHandlerProperty(modelProperty.name());
1566     newModelProperty.setSource(javascript);
1567 }
1568
1569 void ModelAmender::shouldBeNodeListProperty(AbstractProperty &modelProperty,
1570                                             const QList<UiObjectMember *> arrayMembers,
1571                                             ReadingContext *context)
1572 {
1573     ModelNode theNode = modelProperty.parentModelNode();
1574     NodeListProperty newNodeListProperty = theNode.nodeListProperty(modelProperty.name());
1575     m_merger->syncNodeListProperty(newNodeListProperty,
1576                                    arrayMembers,
1577                                    context,
1578                                    *this);
1579 }
1580
1581
1582
1583 void ModelAmender::variantValuesDiffer(VariantProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicType)
1584 {
1585 //    qDebug()<< "ModelAmender::variantValuesDiffer for property"<<modelProperty.name()
1586 //            << "in node" << modelProperty.parentModelNode().id()
1587 //            << ", old value:" << modelProperty.value()
1588 //            << "new value:" << qmlVariantValue;
1589
1590     if (dynamicType.isEmpty())
1591         modelProperty.setValue(qmlVariantValue);
1592     else
1593         modelProperty.setDynamicTypeNameAndValue(dynamicType, qmlVariantValue);
1594 }
1595
1596 void ModelAmender::shouldBeVariantProperty(AbstractProperty &modelProperty, const QVariant &qmlVariantValue, const TypeName &dynamicTypeName)
1597 {
1598     ModelNode theNode = modelProperty.parentModelNode();
1599     VariantProperty newModelProperty = theNode.variantProperty(modelProperty.name());
1600
1601     if (dynamicTypeName.isEmpty())
1602         newModelProperty.setValue(qmlVariantValue);
1603     else
1604         newModelProperty.setDynamicTypeNameAndValue(dynamicTypeName, qmlVariantValue);
1605 }
1606
1607 void ModelAmender::shouldBeNodeProperty(AbstractProperty &modelProperty,
1608                                         const TypeName &typeName,
1609                                         int majorVersion,
1610                                         int minorVersion,
1611                                         UiObjectMember *astNode,
1612                                         ReadingContext *context)
1613 {
1614     ModelNode theNode = modelProperty.parentModelNode();
1615     NodeProperty newNodeProperty = theNode.nodeProperty(modelProperty.name());
1616
1617     const bool propertyTakesComponent = propertyIsComponentType(newNodeProperty, typeName, theNode.model());
1618
1619     const ModelNode &newNode = m_merger->createModelNode(typeName,
1620                                                           majorVersion,
1621                                                           minorVersion,
1622                                                           propertyTakesComponent,
1623                                                           astNode,
1624                                                           context,
1625                                                           *this);
1626
1627     newNodeProperty.setModelNode(newNode);
1628
1629     if (propertyTakesComponent)
1630         m_merger->setupComponentDelayed(newNode, true);
1631
1632 }
1633
1634 void ModelAmender::modelNodeAbsentFromQml(ModelNode &modelNode)
1635 {
1636     modelNode.destroy();
1637 }
1638
1639 ModelNode ModelAmender::listPropertyMissingModelNode(NodeListProperty &modelProperty,
1640                                                      ReadingContext *context,
1641                                                      UiObjectMember *arrayMember)
1642 {
1643     UiQualifiedId *astObjectType = 0;
1644     UiObjectInitializer *astInitializer = 0;
1645     if (UiObjectDefinition *def = cast<UiObjectDefinition *>(arrayMember)) {
1646         astObjectType = def->qualifiedTypeNameId;
1647         astInitializer = def->initializer;
1648     } else if (UiObjectBinding *bin = cast<UiObjectBinding *>(arrayMember)) {
1649         astObjectType = bin->qualifiedTypeNameId;
1650         astInitializer = bin->initializer;
1651     }
1652
1653     if (!astObjectType || !astInitializer)
1654         return ModelNode();
1655
1656     QString typeNameString;
1657     QString fullTypeName;
1658     QString dummy;
1659     int majorVersion;
1660     int minorVersion;
1661     context->lookup(astObjectType, typeNameString, majorVersion, minorVersion, dummy);
1662
1663     TypeName typeName = typeNameString.toUtf8();
1664
1665     if (typeName.isEmpty()) {
1666         qWarning() << "Skipping node with unknown type" << toString(astObjectType);
1667         return ModelNode();
1668     }
1669
1670     const bool propertyTakesComponent = propertyIsComponentType(modelProperty, typeName, m_merger->view()->model());
1671
1672
1673     const ModelNode &newNode = m_merger->createModelNode(typeName,
1674                                                          majorVersion,
1675                                                          minorVersion,
1676                                                          propertyTakesComponent,
1677                                                          arrayMember,
1678                                                          context,
1679                                                          *this);
1680
1681
1682     if (propertyTakesComponent)
1683         m_merger->setupComponentDelayed(newNode, true);
1684
1685     if (modelProperty.isDefaultProperty() || isComponentType(modelProperty.parentModelNode().type())) { //In the default property case we do some magic
1686         if (modelProperty.isNodeListProperty()) {
1687             modelProperty.reparentHere(newNode);
1688         } else { //The default property could a NodeProperty implicitly (delegate:)
1689             modelProperty.parentModelNode().removeProperty(modelProperty.name());
1690             modelProperty.reparentHere(newNode);
1691         }
1692     } else {
1693         modelProperty.reparentHere(newNode);
1694     }
1695     return newNode;
1696 }
1697
1698 void ModelAmender::typeDiffers(bool isRootNode,
1699                                ModelNode &modelNode,
1700                                const TypeName &typeName,
1701                                int majorVersion,
1702                                int minorVersion,
1703                                QmlJS::AST::UiObjectMember *astNode,
1704                                ReadingContext *context)
1705 {
1706     const bool propertyTakesComponent = modelNode.hasParentProperty() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
1707
1708     if (isRootNode) {
1709         modelNode.view()->changeRootNodeType(typeName, majorVersion, minorVersion);
1710     } else {
1711         NodeAbstractProperty parentProperty = modelNode.parentProperty();
1712         int nodeIndex = -1;
1713         if (parentProperty.isNodeListProperty()) {
1714             nodeIndex = parentProperty.toNodeListProperty().toModelNodeList().indexOf(modelNode);
1715             Q_ASSERT(nodeIndex >= 0);
1716         }
1717
1718         modelNode.destroy();
1719
1720         const ModelNode &newNode = m_merger->createModelNode(typeName,
1721                                                              majorVersion,
1722                                                              minorVersion,
1723                                                              propertyTakesComponent,
1724                                                              astNode,
1725                                                              context,
1726                                                              *this);
1727         parentProperty.reparentHere(newNode);
1728         if (parentProperty.isNodeListProperty()) {
1729             int currentIndex = parentProperty.toNodeListProperty().toModelNodeList().indexOf(newNode);
1730             if (nodeIndex != currentIndex)
1731                 parentProperty.toNodeListProperty().slide(currentIndex, nodeIndex);
1732         }
1733     }
1734 }
1735
1736 void ModelAmender::propertyAbsentFromQml(AbstractProperty &modelProperty)
1737 {
1738     modelProperty.parentModelNode().removeProperty(modelProperty.name());
1739 }
1740
1741 void ModelAmender::idsDiffer(ModelNode &modelNode, const QString &qmlId)
1742 {
1743     modelNode.setId(qmlId);
1744 }
1745
1746 void TextToModelMerger::setupComponent(const ModelNode &node)
1747 {
1748     if (!node.isValid())
1749         return;
1750
1751     QString componentText = m_rewriterView->extractText(QList<ModelNode>() << node).value(node);
1752
1753     if (componentText.isEmpty())
1754         return;
1755
1756     QString result = extractComponentFromQml(componentText);
1757
1758     if (result.isEmpty())
1759         return; //No object definition found
1760
1761     if (node.nodeSource() != result)
1762         ModelNode(node).setNodeSource(result);
1763 }
1764
1765 void TextToModelMerger::setupComponentDelayed(const ModelNode &node, bool synchron)
1766 {
1767     if (synchron) {
1768         setupComponent(node);
1769     } else {
1770         m_setupComponentList.insert(node);
1771         m_setupTimer.start();
1772     }
1773 }
1774
1775 void TextToModelMerger::setupCustomParserNode(const ModelNode &node)
1776 {
1777     if (!node.isValid())
1778         return;
1779
1780     QString modelText = m_rewriterView->extractText(QList<ModelNode>() << node).value(node);
1781
1782     if (modelText.isEmpty())
1783         return;
1784
1785     if (node.nodeSource() != modelText)
1786         ModelNode(node).setNodeSource(modelText);
1787
1788 }
1789
1790 void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool synchron)
1791 {
1792     Q_ASSERT(isCustomParserType(node.type()));
1793
1794     if (synchron) {
1795         setupCustomParserNode(node);
1796     } else {
1797         m_setupCustomParserList.insert(node);
1798         m_setupTimer.start();
1799     }
1800 }
1801
1802 void TextToModelMerger::delayedSetup()
1803 {
1804     foreach (const ModelNode node, m_setupComponentList)
1805         setupComponent(node);
1806
1807     foreach (const ModelNode node, m_setupCustomParserList)
1808         setupCustomParserNode(node);
1809         m_setupCustomParserList.clear();
1810         m_setupComponentList.clear();
1811 }
1812
1813 QString TextToModelMerger::textAt(const Document::Ptr &doc,
1814                                   const SourceLocation &location)
1815 {
1816     return doc->source().mid(location.offset, location.length);
1817 }
1818
1819 QString TextToModelMerger::textAt(const Document::Ptr &doc,
1820                                   const SourceLocation &from,
1821                                   const SourceLocation &to)
1822 {
1823     return doc->source().mid(from.offset, to.end() - from.begin());
1824 }