texttomodelmerger.cpp 63.5 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
8
9
10
11
**
**
** GNU Lesser General Public License Usage
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.nokia.com.
30
31
32
33
34
**
**************************************************************************/

#include "abstractproperty.h"
#include "bindingproperty.h"
35
36
#include "filemanager/firstdefinitionfinder.h"
#include "filemanager/objectlengthcalculator.h"
37
38
#include "filemanager/qmlrefactoring.h"
#include "rewriteaction.h"
39
#include "nodeproperty.h"
40
#include "propertyparser.h"
41
#include "textmodifier.h"
42
43
44
#include "texttomodelmerger.h"
#include "rewriterview.h"
#include "variantproperty.h"
45
#include "nodemetainfo.h"
46

47
#include <languageutils/componentversion.h>
48
#include <qmljs/qmljsevaluate.h>
49
#include <qmljs/qmljsinterpreter.h>
50
51
#include <qmljs/qmljslink.h>
#include <qmljs/qmljsscopebuilder.h>
52
#include <qmljs/parser/qmljsast_p.h>
53
#include <qmljs/qmljscheck.h>
54

55
#include <QtCore/QSet>
Thomas Hartmann's avatar
Thomas Hartmann committed
56
#include <QtGui/QMessageBox>
57
#include <QDir>
58

59
using namespace LanguageUtils;
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using namespace QmlJS;
using namespace QmlJS::AST;

namespace {

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;
}

74
static inline QString deEscape(const QString &value)
75
76
77
78
79
80
81
82
83
84
85
{
    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;
}
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

static inline unsigned char convertHex(ushort c)
{
    if (c >= '0' && c <= '9')
        return (c - '0');
    else if (c >= 'a' && c <= 'f')
        return (c - 'a' + 10);
    else
        return (c - 'A' + 10);
}

static inline unsigned char convertHex(ushort c1, ushort c2)
{
    return ((convertHex(c1) << 4) + convertHex(c2));
}

QChar convertUnicode(ushort c1, ushort c2,
                             ushort c3, ushort c4)
{
    return QChar((convertHex(c3) << 4) + convertHex(c4),
                  (convertHex(c1) << 4) + convertHex(c2));
}

static inline bool isHexDigit(ushort c)
{
    return ((c >= '0' && c <= '9')
            || (c >= 'a' && c <= 'f')
            || (c >= 'A' && c <= 'F'));
}


static inline QString fixEscapedUnicodeChar(const QString &value) //convert "\u2939"
{
    if (value.count() == 6 && value.at(0) == '\\' && value.at(1) == 'u' &&
        isHexDigit(value.at(2).unicode()) && isHexDigit(value.at(3).unicode()) &&
        isHexDigit(value.at(4).unicode()) && isHexDigit(value.at(5).unicode())) {
            return convertUnicode(value.at(2).unicode(), value.at(3).unicode(), value.at(4).unicode(), value.at(5).unicode());
    }
    return value;
}
126
 
127
static inline int fixUpMajorVersionForQt(const QString &value, int i)
128
{
129
130
    if (i == 4 && value == "Qt")
        return 1;
131
132
133
    else return i;
}

134
static inline int fixUpMinorVersionForQt(const QString &value, int i)
135
{
136
137
    if (i == 7 && value == "Qt")
        return 0;
138
139
    else return i;
}
140

141
static inline QString fixUpPackeNameForQt(const QString &value)
142
{
143
144
    if (value == "Qt")
        return "QtQuick";
145
146
    return value;
}
147

148
149
150
151
152
153
154
static inline bool isSignalPropertyName(const QString &signalName)
{
    // see QmlCompiler::isSignalPropertyName
    return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) &&
           signalName.at(2).isLetter();
}

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
static inline QVariant cleverConvert(const QString &value)
{
    if (value == "true")
        return QVariant(true);
    if (value == "false")
        return QVariant(false);
    bool flag;
    int i = value.toInt(&flag);
    if (flag)
        return QVariant(i);
    double d = value.toDouble(&flag);
    if (flag)
        return QVariant(d);
    return QVariant(value);
}

171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
static QString flatten(UiQualifiedId *qualifiedId)
{
    QString result;

    for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
        if (!iter->name)
            continue;

        if (!result.isEmpty())
            result.append(QLatin1Char('.'));

        result.append(iter->name->asString());
    }

    return result;
}

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;
}

206
static bool isLiteralValue(Statement *stmt)
207
{
208
    ExpressionStatement *exprStmt = cast<ExpressionStatement *>(stmt);
209
210
211
212
    if (exprStmt)
        return isLiteralValue(exprStmt->expression);
    else
        return false;
213
214
}

215
216
217
218
219
220
221
222
static inline bool isLiteralValue(UiScriptBinding *script)
{
    if (!script || !script->statement)
        return false;

    return isLiteralValue(script->statement);
}

223
224
225
226
227
228
229
230
231
232
233
234
static inline int propertyType(const QString &typeName)
{
    if (typeName == QLatin1String("bool"))
        return QMetaType::type("bool");
    else if (typeName == QLatin1String("color"))
        return QMetaType::type("QColor");
    else if (typeName == QLatin1String("date"))
        return QMetaType::type("QDate");
    else if (typeName == QLatin1String("int"))
        return QMetaType::type("int");
    else if (typeName == QLatin1String("real"))
        return QMetaType::type("double");
235
236
    else if (typeName == QLatin1String("double"))
        return QMetaType::type("double");
237
238
239
240
    else if (typeName == QLatin1String("string"))
        return QMetaType::type("QString");
    else if (typeName == QLatin1String("url"))
        return QMetaType::type("QUrl");
241
    else if (typeName == QLatin1String("var") || typeName == QLatin1String("variant"))
242
243
244
245
246
        return QMetaType::type("QVariant");
    else
        return -1;
}

247
248
249
static inline QVariant convertDynamicPropertyValueToVariant(const QString &astValue,
                                                            const QString &astType)
{
250
    const QString cleanedValue = fixEscapedUnicodeChar(deEscape(stripQuotes(astValue.trimmed())));
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267

    if (astType.isEmpty())
        return QString();

    const int type = propertyType(astType);
    if (type == QMetaType::type("QVariant")) {
        if (cleanedValue.isNull()) // Explicitly isNull, NOT isEmpty!
            return QVariant(static_cast<QVariant::Type>(type));
        else
            return QVariant(cleanedValue);
    } else {
        QVariant value = QVariant(cleanedValue);
        value.convert(static_cast<QVariant::Type>(type));
        return value;
    }
}

268
269
static bool isComponentType(const QString &type)
{
270
    return  type == QLatin1String("Component") || type == QLatin1String("Qt.Component") || type == QLatin1String("QtQuick.Component");
271
272
}

273
static bool isCustomParserType(const QString &type)
274
{
275
276
277
278
279
280
281
282
283
    return type == "QtQuick.VisualItemModel" || type == "Qt.VisualItemModel" ||
           type == "QtQuick.VisualDataModel" || type == "Qt.VisualDataModel" ||
           type == "QtQuick.ListModel" || type == "Qt.ListModel" ||
           type == "QtQuick.XmlListModel" || type == "Qt.XmlListModel";
}


static bool isPropertyChangesType(const QString &type)
{    
284
    return  type == QLatin1String("PropertyChanges") || type == QLatin1String("QtQuick.PropertyChanges") || type == QLatin1String("Qt.PropertyChanges");
285
286
}

287
static bool propertyIsComponentType(const QmlDesigner::NodeAbstractProperty &property, const QString &type, QmlDesigner::Model *model)
288
{
289
    if (model->metaInfo(type, -1, -1).isSubclassOf(QLatin1String("QtQuick.Component"), -1, -1) && !isComponentType(type)) {
290
291
292
        return false; //If the type is already a subclass of Component keep it
    }

293
294
295
296
    return property.parentModelNode().isValid() &&
            isComponentType(property.parentModelNode().metaInfo().propertyTypeName(property.name()));
}

297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
static inline QString extractComponentFromQml(const QString &source)
{
    if (source.isEmpty())
        return QString();

    QString result;
    if (source.contains("Component")) { //explicit component
        QmlDesigner::FirstDefinitionFinder firstDefinitionFinder(source);
        int offset = firstDefinitionFinder(0);
        if (offset < 0) {
            return QString(); //No object definition found
        }
        QmlDesigner::ObjectLengthCalculator objectLengthCalculator;
        unsigned length;
        if (objectLengthCalculator(source, offset, length)) {
            result = source.mid(offset, length);
        } else {
            result = source;
        }
    } else {
        result = source; //implicit component
    }
    return result;
}
321

322
323
} // anonymous namespace

324
325
326
327
328
329
330
331
332
333
namespace QmlDesigner {
namespace Internal {

class ReadingContext
{
public:
    ReadingContext(const Snapshot &snapshot, const Document::Ptr &doc,
                   const QStringList importPaths)
        : m_snapshot(snapshot)
        , m_doc(doc)
334
        , m_context(new Interpreter::Context(snapshot))
335
        , m_link(m_context, snapshot, importPaths)
336
        , m_scopeBuilder(m_context, doc)
337
    {
338
        m_link(doc, &m_diagnosticLinkMessages);
339
340
341
        m_lookupContext = LookupContext::create(doc, *m_context, QList<AST::Node*>());
        // cheaper than calling m_scopeBuilder.initializeRootScope()
        *m_context = *m_lookupContext->context();
342
343
344
    }

    ~ReadingContext()
345
    { delete m_context; }
346
347
348
349
350
351
352
353
354
355
356
357
358
359

    Document::Ptr doc() const
    { return m_doc; }

    void enterScope(Node *node)
    { m_scopeBuilder.push(node); }

    void leaveScope()
    { m_scopeBuilder.pop(); }

    void lookup(UiQualifiedId *astTypeNode, QString &typeName, int &majorVersion,
                int &minorVersion, QString &defaultPropertyName)
    {
        const Interpreter::ObjectValue *value = m_context->lookupType(m_doc.data(), astTypeNode);
360
361
        defaultPropertyName = m_context->defaultPropertyName(value);

362
363
        const Interpreter::QmlObjectValue * qmlValue = dynamic_cast<const Interpreter::QmlObjectValue *>(value);
        if (qmlValue) {
364
            typeName = fixUpPackeNameForQt(qmlValue->packageName()) + QLatin1String(".") + qmlValue->className();
365
366

            //### todo this is just a hack to support QtQuick 1.0
367
368
            majorVersion = fixUpMajorVersionForQt(qmlValue->packageName(), qmlValue->version().majorVersion());
            minorVersion = fixUpMinorVersionForQt(qmlValue->packageName(), qmlValue->version().minorVersion());
369
        } else {
370
371
372
            for (UiQualifiedId *iter = astTypeNode; iter; iter = iter->next)
                if (!iter->next && iter->name)
                    typeName = iter->name->asString();
373
374
375
376
377
378
379
380
381

            QString fullTypeName;
            for (UiQualifiedId *iter = astTypeNode; iter; iter = iter->next)
                if (iter->name)
                    fullTypeName += iter->name->asString() + ".";

            if (fullTypeName.endsWith("."))
                fullTypeName.chop(1);

382
383
            majorVersion = ComponentVersion::NoVersion;
            minorVersion = ComponentVersion::NoVersion;
384

385
386
            const Interpreter::Imports *imports = m_lookupContext->context()->imports(m_lookupContext->document().data());
            Interpreter::ImportInfo importInfo = imports->info(fullTypeName, m_context);
387
388
389
390
391
392
393
394
395
396
397
398
399
            if (importInfo.isValid() && importInfo.type() == Interpreter::ImportInfo::LibraryImport) {
                QString name = importInfo.name().replace("\\", ".");
                majorVersion = importInfo.version().majorVersion();
                minorVersion = importInfo.version().minorVersion();
                typeName.prepend(name + ".");
            } else if (importInfo.isValid() && importInfo.type() == Interpreter::ImportInfo::DirectoryImport) {
                QString path = importInfo.name();
                QDir dir(m_doc->path());
                QString relativeDir = dir.relativeFilePath(path);
                QString name = relativeDir.replace("/", ".");               
                if (!name.isEmpty())
                    typeName.prepend(name + ".");
            }
400
401
402
403
404
405
        }
    }

    /// When something is changed here, also change Check::checkScopeObjectMember in
    /// qmljscheck.cpp
    /// ### Maybe put this into the context as a helper method.
406
    bool lookupProperty(const QString &prefix, const UiQualifiedId *id, const Interpreter::Value **property = 0, const Interpreter::ObjectValue **parentObject = 0, QString *name = 0)
407
408
409
410
411
412
413
414
415
416
417
    {
        QList<const Interpreter::ObjectValue *> scopeObjects = m_context->scopeChain().qmlScopeObjects;
        if (scopeObjects.isEmpty())
            return false;

        if (! id)
            return false; // ### error?

        if (! id->name) // possible after error recovery
            return false;

418
419
420
421
422
423
        QString propertyName;
        if (prefix.isEmpty())
            propertyName = id->name->asString();
        else
            propertyName = prefix;

424
425
426
427
428
429
430
431
432
433
        if (name)
            *name = propertyName;

        if (propertyName == QLatin1String("id") && ! id->next)
            return false; // ### should probably be a special value

        // attached properties
        bool isAttachedProperty = false;
        if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
            isAttachedProperty = true;
434
            if (const Interpreter::ObjectValue *qmlTypes = m_context->scopeChain().qmlTypes)
435
                scopeObjects += qmlTypes;
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
        }

        if (scopeObjects.isEmpty())
            return false;

        // global lookup for first part of id
        const Interpreter::ObjectValue *objectValue = 0;
        const Interpreter::Value *value = 0;
        for (int i = scopeObjects.size() - 1; i >= 0; --i) {
            objectValue = scopeObjects[i];
            value = objectValue->lookupMember(propertyName, m_context);
            if (value)
                break;
        }
        if (parentObject)
            *parentObject = objectValue;
        if (!value) {
            qWarning() << "Skipping invalid property name" << propertyName;
            return false;
        }

        // can't look up members for attached properties
        if (isAttachedProperty)
            return false;

461
462
463
464
        // resolve references
        if (const Interpreter::Reference *ref = value->asReference())
            value = m_context->lookupReference(ref);

465
466
        // member lookup
        const UiQualifiedId *idPart = id;
467
468
469
        if (prefix.isEmpty())
            idPart = idPart->next;
        for (; idPart; idPart = idPart->next) {
470
471
472
473
474
475
476
477
478
479
            objectValue = Interpreter::value_cast<const Interpreter::ObjectValue *>(value);
            if (! objectValue) {
//                if (idPart->name)
//                    qDebug() << idPart->name->asString() << "has no property named"
//                             << propertyName;
                return false;
            }
            if (parentObject)
                *parentObject = objectValue;

480
            if (! idPart->name) {
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
                // somebody typed "id." and error recovery still gave us a valid tree,
                // so just bail out here.
                return false;
            }

            propertyName = idPart->name->asString();
            if (name)
                *name = propertyName;

            value = objectValue->lookupMember(propertyName, m_context);
            if (! value) {
//                if (idPart->name)
//                    qDebug() << "In" << idPart->name->asString() << ":"
//                             << objectValue->className() << "has no property named"
//                             << propertyName;
                return false;
            }
        }

        if (property)
            *property = value;
        return true;
    }

505
    bool isArrayProperty(const Interpreter::Value *value, const Interpreter::ObjectValue *containingObject, const QString &name)
506
507
508
509
    {
        if (!value)
            return false;
        const Interpreter::ObjectValue *objectValue = value->asObjectValue();
510
        if (objectValue && objectValue->prototype(m_context) == m_context->engine()->arrayPrototype())
511
            return true;
512

513
514
515
        Interpreter::PrototypeIterator iter(containingObject, m_context);
        while (iter.hasNext()) {
            const Interpreter::ObjectValue *proto = iter.next();
516
            if (proto->lookupMember(name, m_context) == m_context->engine()->arrayPrototype())
517
                return true;
518
            if (const Interpreter::QmlObjectValue *qmlIter = dynamic_cast<const Interpreter::QmlObjectValue *>(proto)) {
519
520
521
522
523
                if (qmlIter->isListProperty(name))
                    return true;
            }
        }
        return false;
524
525
    }

526
    QVariant convertToVariant(const QString &astValue, const QString &propertyPrefix, UiQualifiedId *propertyId)
527
    {
528
        const bool hasQuotes = astValue.trimmed().left(1) == QLatin1String("\"") && astValue.trimmed().right(1) == QLatin1String("\"");
529
        const QString cleanedValue = fixEscapedUnicodeChar(deEscape(stripQuotes(astValue.trimmed())));
530
531
532
        const Interpreter::Value *property = 0;
        const Interpreter::ObjectValue *containingObject = 0;
        QString name;
533
534
        if (!lookupProperty(propertyPrefix, propertyId, &property, &containingObject, &name)) {
            qWarning() << "Unknown property" << propertyPrefix + QLatin1Char('.') + flatten(propertyId)
535
536
                       << "on line" << propertyId->identifierToken.startLine
                       << "column" << propertyId->identifierToken.startColumn;
537
            return hasQuotes ? QVariant(cleanedValue) : cleverConvert(cleanedValue);
538
539
        }

540
541
        if (containingObject)
            containingObject->lookupMember(name, m_context, &containingObject);
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565

        if (const Interpreter::QmlObjectValue * qmlObject = dynamic_cast<const Interpreter::QmlObjectValue *>(containingObject)) {
            const QString typeName = qmlObject->propertyType(name);
            if (qmlObject->isEnum(typeName)) {
                return QVariant(cleanedValue);
            } else {
                int type = QMetaType::type(typeName.toUtf8().constData());
                QVariant result;
                if (type)
                    result = PropertyParser::read(type, cleanedValue);
                if (result.isValid())
                    return result;
            }
        }

        QVariant v(cleanedValue);
        if (property->asBooleanValue()) {
            v.convert(QVariant::Bool);
        } else if (property->asColorValue()) {
            v.convert(QVariant::Color);
        } else if (property->asNumberValue()) {
            v.convert(QVariant::Double);
        } else if (property->asStringValue()) {
            // nothing to do
566
567
568
        } else { //property alias et al
            if (!hasQuotes)
                return cleverConvert(cleanedValue);
569
570
571
572
        }
        return v;
    }

573
    QVariant convertToEnum(Statement *rhs, const QString &propertyPrefix, UiQualifiedId *propertyId)
574
575
576
577
578
579
580
    {
        ExpressionStatement *eStmt = cast<ExpressionStatement *>(rhs);
        if (!eStmt || !eStmt->expression)
            return QVariant();

        const Interpreter::ObjectValue *containingObject = 0;
        QString name;
581
        if (!lookupProperty(propertyPrefix, propertyId, 0, &containingObject, &name)) {
582
583
584
            return QVariant();
        }

585
586
        if (containingObject)
            containingObject->lookupMember(name, m_context, &containingObject);
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
        const Interpreter::QmlObjectValue * lhsQmlObject = dynamic_cast<const Interpreter::QmlObjectValue *>(containingObject);
        if (!lhsQmlObject)
            return QVariant();
        const QString lhsPropertyTypeName = lhsQmlObject->propertyType(name);

        const Interpreter::ObjectValue *rhsValueObject = 0;
        QString rhsValueName;
        if (IdentifierExpression *idExp = cast<IdentifierExpression *>(eStmt->expression)) {
            if (!m_context->scopeChain().qmlScopeObjects.isEmpty())
                rhsValueObject = m_context->scopeChain().qmlScopeObjects.last();
            if (idExp->name)
                rhsValueName = idExp->name->asString();
        } else if (FieldMemberExpression *memberExp = cast<FieldMemberExpression *>(eStmt->expression)) {
            Evaluate evaluate(m_context);
            const Interpreter::Value *result = evaluate(memberExp->base);
            rhsValueObject = result->asObjectValue();

            if (memberExp->name)
                rhsValueName = memberExp->name->asString();
        }

608
609
        if (rhsValueObject)
            rhsValueObject->lookupMember(rhsValueName, m_context, &rhsValueObject);
610
611
612
613
614
615
616
617
618
619
620

        const Interpreter::QmlObjectValue *rhsQmlObjectValue = dynamic_cast<const Interpreter::QmlObjectValue *>(rhsValueObject);
        if (!rhsQmlObjectValue)
            return QVariant();

        if (rhsQmlObjectValue->enumContainsKey(lhsPropertyTypeName, rhsValueName))
            return QVariant(rhsValueName);
        else
            return QVariant();
    }

621
622
623
624

    LookupContext::Ptr lookupContext() const
    { return m_lookupContext; }

625
    QList<DiagnosticMessage> diagnosticLinkMessages() const
626
    { return m_diagnosticLinkMessages; }
627

628
629
630
631
private:
    Snapshot m_snapshot;
    Document::Ptr m_doc;
    Interpreter::Context *m_context;
632
    QList<DiagnosticMessage> m_diagnosticLinkMessages;
633
    Link m_link;
634
    LookupContext::Ptr m_lookupContext;
635
636
637
638
639
640
641
642
643
    ScopeBuilder m_scopeBuilder;
};

} // namespace Internal
} // namespace QmlDesigner

using namespace QmlDesigner;
using namespace QmlDesigner::Internal;

644
645
646

static inline bool smartVeryFuzzyCompare(QVariant value1, QVariant value2)
{ //we ignore slight changes on doubles and only check three digits
647
648
649
650
651
652
653
    if ((value1.type() == QVariant::Double) || (value2.type() == QVariant::Double)) {
        bool ok1, ok2;
        int a = value1.toDouble(&ok1) * 1000;
        int b = value2.toDouble(&ok2) * 1000;

        if (!ok1 || !ok2)
            return false;
654
655
656
657
658
659
660
661

        if (qFuzzyCompare((qreal(a) / 1000), (qreal(b) / 1000))) {
            return true;
        }
    }
    return false;
}

662
663
static inline bool equals(const QVariant &a, const QVariant &b)
{
664
665
    if (smartVeryFuzzyCompare(a, b))
        return true;
666
667
668
669
    else
        return a == b;
}

670
TextToModelMerger::TextToModelMerger(RewriterView *reWriterView) :
671
672
673
        m_rewriterView(reWriterView),
        m_isActive(false)
{
674
    Q_ASSERT(reWriterView);
675
676
    m_setupTimer.setSingleShot(true);
    RewriterView::connect(&m_setupTimer, SIGNAL(timeout()), reWriterView, SLOT(delayedSetup()));
677
678
679
680
681
682
683
684
685
686
687
688
}

void TextToModelMerger::setActive(bool active)
{
    m_isActive = active;
}

bool TextToModelMerger::isActive() const
{
    return m_isActive;
}

689
void TextToModelMerger::setupImports(const Document::Ptr &doc,
690
                                     DifferenceHandler &differenceHandler)
691
{
692
    QList<Import> existingImports = m_rewriterView->model()->imports();
693

694
695
696
697
698
699
700
701
702
703
704
705
706
707
    for (UiImportList *iter = doc->qmlProgram()->imports; iter; iter = iter->next) {
        UiImport *import = iter->import;
        if (!import)
            continue;

        QString version;
        if (import->versionToken.isValid())
            version = textAt(doc, import->versionToken);
        QString as;
        if (import->importId)
            as = import->importId->asString();

        if (import->fileName) {
            const QString strippedFileName = stripQuotes(import->fileName->asString());
708
            const Import newImport = Import::createFileImport(strippedFileName,
709
                                                              version, as, m_rewriterView->textModifier()->importPaths());
710

711
            if (!existingImports.removeOne(newImport))
712
713
                differenceHandler.modelMissesImport(newImport);
        } else {
714
            QString importUri = flatten(import->importUri);
715
716
717
            if (importUri == QLatin1String("Qt") && version == QLatin1String("4.7")) {
                importUri = QLatin1String("QtQuick");
                version = QLatin1String("1.0");
718
719
            }

720
            const Import newImport =
721
                    Import::createLibraryImport(importUri, version, as, m_rewriterView->textModifier()->importPaths());
722

723
            if (!existingImports.removeOne(newImport))
724
                differenceHandler.modelMissesImport(newImport);
725
726
        }
    }
727
728

    foreach (const Import &import, existingImports)
729
        differenceHandler.importAbsentInQMl(import);
730
731
}

732
bool TextToModelMerger::load(const QString &data, DifferenceHandler &differenceHandler)
733
{
734
735
//    qDebug() << "TextToModelMerger::load with data:" << data;

736
737
    const QUrl url = m_rewriterView->model()->fileUrl();
    const QStringList importPaths = m_rewriterView->textModifier()->importPaths();
738
739
    setActive(true);

740
741
742
743

    try {
        Snapshot snapshot = m_rewriterView->textModifier()->getSnapshot();
        const QString fileName = url.toLocalFile();
Erik Verbruggen's avatar
Erik Verbruggen committed
744
        Document::Ptr doc = Document::create(fileName.isEmpty() ? QLatin1String("<internal>") : fileName);
745
        doc->setSource(data);
746
        doc->parseQml();
747
748
749
750
751
752
753
754
755

        if (!doc->isParsedCorrectly()) {
            QList<RewriterView::Error> errors;
            foreach (const QmlJS::DiagnosticMessage &message, doc->diagnosticMessages())
                errors.append(RewriterView::Error(message, QUrl::fromLocalFile(doc->fileName())));
            m_rewriterView->setErrors(errors);
            setActive(false);
            return false;
        }
756
757
        snapshot.insert(doc);
        ReadingContext ctxt(snapshot, doc, importPaths);
758
759
        m_lookupContext = ctxt.lookupContext();
        m_document = doc;
760

761
        QList<RewriterView::Error> errors;
762
763
764
765
766

        foreach (const QmlJS::DiagnosticMessage &diagnosticMessage, ctxt.diagnosticLinkMessages()) {
            errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName())));
        }

767
768
        setupImports(doc, differenceHandler);

769
770
771
772
773
        if (m_rewriterView->model()->imports().isEmpty()) {
            const QmlJS::DiagnosticMessage diagnosticMessage(QmlJS::DiagnosticMessage::Error, AST::SourceLocation(0, 0, 0, 0), QCoreApplication::translate("QmlDesigner::TextToModelMerger error message", "No import statements found"));
            errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName())));
        }

774
        if (view()->checkSemanticErrors()) {
775
            Check check(doc, m_lookupContext->context());
776
            check.setOptions(check.options() & ~Check::ErrCheckTypeErrors);
777
            foreach (const QmlJS::DiagnosticMessage &diagnosticMessage, check()) {
778
779
                if (diagnosticMessage.isError())
                    errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName())));
780
            }
781
782
783
784
785
786

            if (!errors.isEmpty()) {
                m_rewriterView->setErrors(errors);
                setActive(false);
                return false;
            }
787
788
        }

789
790
791
792
793
794
795
796
        UiObjectMember *astRootNode = 0;
        if (UiProgram *program = doc->qmlProgram())
            if (program->members)
                astRootNode = program->members->member;
        ModelNode modelRootNode = m_rewriterView->rootModelNode();
        syncNode(modelRootNode, astRootNode, &ctxt, differenceHandler);
        m_rewriterView->positionStorage()->cleanupInvalidOffsets();
        m_rewriterView->clearErrors();
797
798

        setActive(false);
799
        return true;
800
    } catch (Exception &e) {
801
        RewriterView::Error error(&e);
802
803
804
805
        // Somehow, the error below gets eaten in upper levels, so printing the
        // exception info here for debugging purposes:
        qDebug() << "*** An exception occurred while reading the QML file:"
                 << error.toString();
806
        m_rewriterView->addError(error);
807
808
809

        setActive(false);

810
        return false;
811
812
813
    }
}

814
void TextToModelMerger::syncNode(ModelNode &modelNode,
815
816
                                 UiObjectMember *astNode,
                                 ReadingContext *context,
817
                                 DifferenceHandler &differenceHandler)
818
{
819
820
821
822
823
824
825
826
827
828
829
830
831
    UiQualifiedId *astObjectType = 0;
    UiObjectInitializer *astInitializer = 0;
    if (UiObjectDefinition *def = cast<UiObjectDefinition *>(astNode)) {
        astObjectType = def->qualifiedTypeNameId;
        astInitializer = def->initializer;
    } else if (UiObjectBinding *bin = cast<UiObjectBinding *>(astNode)) {
        astObjectType = bin->qualifiedTypeNameId;
        astInitializer = bin->initializer;
    }

    if (!astObjectType || !astInitializer)
        return;

Erik Verbruggen's avatar
Erik Verbruggen committed
832
    m_rewriterView->positionStorage()->setNodeOffset(modelNode, astObjectType->identifierToken.offset);
833

834
    QString typeName, defaultPropertyName;
835
836
    int majorVersion;
    int minorVersion;
837
    context->lookup(astObjectType, typeName, majorVersion, minorVersion, defaultPropertyName);
838
839
    if (defaultPropertyName.isEmpty()) //fallback and use the meta system of the model
        defaultPropertyName = modelNode.metaInfo().defaultPropertyName();
840
841
842
843
844

    if (typeName.isEmpty()) {
        qWarning() << "Skipping node with unknown type" << flatten(astObjectType);
        return;
    }
845

846
    bool isImplicitComponent = modelNode.parentProperty().isValid() && propertyIsComponentType(modelNode.parentProperty(), typeName, modelNode.model());
847

848

849
850
851
    if (modelNode.type() != typeName //If there is no valid parentProperty                                                                                                      //the node has just been created. The type is correct then.
            || modelNode.majorVersion() != majorVersion
            || modelNode.minorVersion() != minorVersion) {
852
        const bool isRootNode = m_rewriterView->rootModelNode() == modelNode;
853
        differenceHandler.typeDiffers(isRootNode, modelNode, typeName,
854
855
                                      majorVersion, minorVersion,
                                      astNode, context);
856
857
858
859
        if (!isRootNode)
            return; // the difference handler will create a new node, so we're done.
    }

860
861
    if (isComponentType(typeName) || isImplicitComponent)
        setupComponentDelayed(modelNode, !differenceHandler.isValidator());
862
863

    if (isCustomParserType(typeName))
864
        setupCustomParserNodeDelayed(modelNode, !differenceHandler.isValidator());
865

866
867
    context->enterScope(astNode);

868
    QSet<QString> modelPropertyNames = QSet<QString>::fromList(modelNode.propertyNames());
869
870
    if (!modelNode.id().isEmpty())
        modelPropertyNames.insert(QLatin1String("id"));
871
    QList<UiObjectMember *> defaultPropertyItems;
872

873
874
875
    for (UiObjectMemberList *iter = astInitializer->members; iter; iter = iter->next) {
        UiObjectMember *member = iter->member;
        if (!member)
876
877
            continue;

878
879
        if (UiArrayBinding *array = cast<UiArrayBinding *>(member)) {
            const QString astPropertyName = flatten(array->qualifiedId);
880
            if (isPropertyChangesType(typeName) || context->lookupProperty(QString(), array->qualifiedId)) {
881
                AbstractProperty modelProperty = modelNode.property(astPropertyName);
Erik Verbruggen's avatar
Erik Verbruggen committed
882
883
884
885
886
887
                QList<UiObjectMember *> arrayMembers;
                for (UiArrayMemberList *iter = array->members; iter; iter = iter->next)
                    if (UiObjectMember *member = iter->member)
                        arrayMembers.append(member);

                syncArrayProperty(modelProperty, arrayMembers, context, differenceHandler);
888
889
890
891
892
                modelPropertyNames.remove(astPropertyName);
            } else {
                qWarning() << "Skipping invalid array property" << astPropertyName
                           << "for node type" << modelNode.type();
            }
893
894
895
896
897
898
899
900
901
902
903
904
905
        } else if (UiObjectDefinition *def = cast<UiObjectDefinition *>(member)) {
            const QString name = def->qualifiedTypeNameId->name->asString();
            if (name.isEmpty() || !name.at(0).isUpper()) {
                QStringList props = syncGroupedProperties(modelNode,
                                                          name,
                                                          def->initializer->members,
                                                          context,
                                                          differenceHandler);
                foreach (const QString &prop, props)
                    modelPropertyNames.remove(prop);
            } else {
                defaultPropertyItems.append(member);
            }
906
907
908
909
910
        } else if (UiObjectBinding *binding = cast<UiObjectBinding *>(member)) {
            const QString astPropertyName = flatten(binding->qualifiedId);
            if (binding->hasOnToken) {
                // skip value sources
            } else {
911
912
913
                const Interpreter::Value *propertyType = 0;
                const Interpreter::ObjectValue *containingObject = 0;
                QString name;
914
                if (context->lookupProperty(QString(), binding->qualifiedId, &propertyType, &containingObject, &name) || isPropertyChangesType(typeName)) {
915
                    AbstractProperty modelProperty = modelNode.property(astPropertyName);
916
                    if (context->isArrayProperty(propertyType, containingObject, name)) {
Erik Verbruggen's avatar
Erik Verbruggen committed
917
                        syncArrayProperty(modelProperty, QList<QmlJS::AST::UiObjectMember*>() << member, context, differenceHandler);
918
919
920
                    } else {
                        syncNodeProperty(modelProperty, binding, context, differenceHandler);
                    }
921
922
923
924
925
926
927
                    modelPropertyNames.remove(astPropertyName);
                } else {
                    qWarning() << "Skipping invalid node property" << astPropertyName
                               << "for node type" << modelNode.type();
                }
            }
        } else if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) {
928
            modelPropertyNames.remove(syncScriptBinding(modelNode, QString(), script, context, differenceHandler));
929
        } else if (UiPublicMember *property = cast<UiPublicMember *>(member)) {
930
931
932
933
934
935
            if (property->type == UiPublicMember::Signal)
                continue; // QML designer doesn't support this yet.

            if (!property->name || !property->memberType)
                continue; // better safe than sorry.

936
937
            const QString astName = property->name->asString();
            QString astValue;
938
            if (property->statement)
939
                astValue = textAt(context->doc(),
940
941
                                  property->statement->firstSourceLocation(),
                                  property->statement->lastSourceLocation());
942
943
            const QString astType = property->memberType->asString();
            AbstractProperty modelProperty = modelNode.property(astName);
944
            if (!property->statement || isLiteralValue(property->statement)) {
945
946
947
                const QVariant variantValue = convertDynamicPropertyValueToVariant(astValue, astType);
                syncVariantProperty(modelProperty, variantValue, astType, differenceHandler);
            } else {
948
                syncExpressionProperty(modelProperty, astValue, astType, differenceHandler);
949
            }
950
951
            modelPropertyNames.remove(astName);
        } else {
dt's avatar
dt committed
952
            qWarning() << "Found an unknown QML value.";
953
954
955
        }
    }

956
    if (!defaultPropertyItems.isEmpty()) {
957
958
        if (isComponentType(modelNode.type()))
            setupComponentDelayed(modelNode, !differenceHandler.isValidator());
959
        if (defaultPropertyName.isEmpty()) {
960
            qWarning() << "No default property for node type" << modelNode.type() << ", ignoring child items.";
961
962
963
964
965
966
        } else {
            AbstractProperty modelProperty = modelNode.property(defaultPropertyName);
            if (modelProperty.isNodeListProperty()) {
                NodeListProperty nodeListProperty = modelProperty.toNodeListProperty();
                syncNodeListProperty(nodeListProperty, defaultPropertyItems, context,
                                     differenceHandler);
967
            } else {
968
969
970
                differenceHandler.shouldBeNodeListProperty(modelProperty,
                                                           defaultPropertyItems,
                                                           context);
971
            }
972
            modelPropertyNames.remove(defaultPropertyName);
973
974
975
976
977
978
        }
    }

    foreach (const QString &modelPropertyName, modelPropertyNames) {
        AbstractProperty modelProperty = modelNode.property(modelPropertyName);

979
        // property deleted.
980
981
982
983
        if (modelPropertyName == QLatin1String("id"))
            differenceHandler.idsDiffer(modelNode, QString());
        else
            differenceHandler.propertyAbsentFromQml(modelProperty);
984
    }
985
986

    context->leaveScope();
987
}
988

989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
QString TextToModelMerger::syncScriptBinding(ModelNode &modelNode,
                                             const QString &prefix,
                                             UiScriptBinding *script,
                                             ReadingContext *context,
                                             DifferenceHandler &differenceHandler)
{
    QString astPropertyName = flatten(script->qualifiedId);
    if (!prefix.isEmpty())
        astPropertyName.prepend(prefix + QLatin1Char('.'));

    QString astValue;
    if (script->statement) {
        astValue = textAt(context->doc(),
                          script->statement->firstSourceLocation(),
                          script->statement->lastSourceLocation());
        astValue = astValue.trimmed();
        if (astValue.endsWith(QLatin1Char(';')))
            astValue = astValue.left(astValue.length() - 1);
        astValue = astValue.trimmed();
    }

    if (astPropertyName == QLatin1String("id")) {
        syncNodeId(modelNode, astValue, differenceHandler);
        return astPropertyName;
    }

    if (isSignalPropertyName(astPropertyName))
        return QString();

    if (isLiteralValue(script)) {
1019
        if (isPropertyChangesType(modelNode.type())) {
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
            AbstractProperty modelProperty = modelNode.property(astPropertyName);
            const QVariant variantValue(deEscape(stripQuotes(astValue)));
            syncVariantProperty(modelProperty, variantValue, QString(), differenceHandler);
            return astPropertyName;
        } else {
            const QVariant variantValue = context->convertToVariant(astValue, prefix, script->qualifiedId);
            if (variantValue.isValid()) {
                AbstractProperty modelProperty = modelNode.property(astPropertyName);
                syncVariantProperty(modelProperty, variantValue, QString(), differenceHandler);
                return astPropertyName;
            } else {
                qWarning() << "Skipping invalid variant property" << astPropertyName
                           << "for node type" << modelNode.type();
                return QString();
            }
        }
    }

    const QVariant enumValue = context->convertToEnum(script->statement, prefix, script->qualifiedId);
    if (enumValue.isValid()) { // It is a qualified enum:
        AbstractProperty modelProperty = modelNode.property(astPropertyName);
1041
        syncVariantProperty(modelProperty, enumValue, QString(), differenceHandler); // TODO: parse type
1042
1043
        return astPropertyName;
    } else { // Not an enum, so:
1044
        if (isPropertyChangesType(modelNode.type()) || context->lookupProperty(prefix, script->qualifiedId)) {
1045
            AbstractProperty modelProperty = modelNode.property(astPropertyName);
1046
            syncExpressionProperty(modelProperty, astValue, QString(), differenceHandler); // TODO: parse type
1047
1048
1049
1050
1051
1052
1053
1054
1055
            return astPropertyName;
        } else {
            qWarning() << "Skipping invalid expression property" << astPropertyName
                    << "for node type" << modelNode.type();
            return QString();
        }
    }
}

1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
void TextToModelMerger::syncNodeId(ModelNode &modelNode, const QString &astObjectId,
                                   DifferenceHandler &differenceHandler)
{
    if (astObjectId.isEmpty()) {
        if (!modelNode.id().isEmpty()) {
            ModelNode existingNodeWithId = m_rewriterView->modelNodeForId(astObjectId);
            if (existingNodeWithId.isValid())
                existingNodeWithId.setId(QString());
            differenceHandler.idsDiffer(modelNode, astObjectId);
        }
    } else {
        if (modelNode.id() != astObjectId) {
            ModelNode existingNodeWithId = m_rewriterView->modelNodeForId(astObjectId);
            if (existingNodeWithId.isValid())
                existingNodeWithId.setId(QString());
            differenceHandler.idsDiffer(modelNode, astObjectId);
1072
1073
1074
1075
        }
    }
}

1076
1077
1078
1079
void TextToModelMerger::syncNodeProperty(AbstractProperty &modelProperty,
                                         UiObjectBinding *binding,
                                         ReadingContext *context,
                                         DifferenceHandler &differenceHandler)
1080
{
1081
    QString typeName, dummy;
1082
1083
    int majorVersion;
    int minorVersion;
1084
    context->lookup(binding->qualifiedTypeNameId, typeName, majorVersion, minorVersion, dummy);
1085

1086
1087
1088
1089
    if (typeName.isEmpty()) {
        qWarning() << "Skipping node with unknown type" << flatten(binding->qualifiedTypeNameId);
        return;
    }
1090

1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
    if (modelProperty.isNodeProperty()) {
        ModelNode nodePropertyNode = modelProperty.toNodeProperty().modelNode();
        syncNode(nodePropertyNode, binding, context, differenceHandler);
    } else {
        differenceHandler.shouldBeNodeProperty(modelProperty,
                                               typeName,
                                               majorVersion,
                                               minorVersion,
                                               binding, context);
    }
}

void TextToModelMerger::syncExpressionProperty(AbstractProperty &modelProperty,
                                               const QString &javascript,
1105
                                               const QString &astType,
1106
1107
1108
1109
                                               DifferenceHandler &differenceHandler)
{
    if (modelProperty.isBindingProperty()) {
        BindingProperty bindingProperty = modelProperty.toBindingProperty();
1110
1111
1112
1113
        if (bindingProperty.expression() != javascript
                || !astType.isEmpty() != bindingProperty.isDynamic()
                || astType != bindingProperty.dynamicTypeName()) {
            differenceHandler.bindingExpressionsDiffer(bindingProperty, javascript, astType);
1114
        }
1115
    } else {
1116
        differenceHandler.shouldBeBindingProperty(modelProperty, javascript, astType);
1117
1118
1119
1120
    }
}

void TextToModelMerger::syncArrayProperty(AbstractProperty &modelProperty,
Erik Verbruggen's avatar
Erik Verbruggen committed
1121
                                          const QList<UiObjectMember *> &arrayMembers,
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
                                          ReadingContext *context,
                                          DifferenceHandler &differenceHandler)
{
    if (modelProperty.isNodeListProperty()) {
        NodeListProperty nodeListProperty = modelProperty.toNodeListProperty();
        syncNodeListProperty(nodeListProperty, arrayMembers, context, differenceHandler);
    } else {
        differenceHandler.shouldBeNodeListProperty(modelProperty,
                                                   arrayMembers,
                                                   context);
    }
}

void TextToModelMerger::syncVariantProperty(AbstractProperty &modelProperty,
1136
                                            const QVariant &astValue,
1137
1138
1139
1140
1141
1142
                                            const QString &astType,
                                            DifferenceHandler &differenceHandler)
{
    if (modelProperty.isVariantProperty()) {
        VariantProperty modelVariantProperty = modelProperty.toVariantProperty();

1143
        if (!equals(modelVariantProperty.value(), astValue)
1144
1145
1146
                || !astType.isEmpty() != modelVariantProperty.isDynamic()
                || astType != modelVariantProperty.dynamicTypeName()) {
            differenceHandler.variantValuesDiffer(modelVariantProperty,
1147
                                                  astValue,
1148
                                                  astType);
1149
1150
        }
    } else {
1151
        differenceHandler.shouldBeVariantProperty(modelProperty,
1152
                                                  astValue,
1153
                                                  astType);
1154
1155
1156
    }
}

1157
1158
1159
1160
void TextToModelMerger::syncNodeListProperty(NodeListProperty &modelListProperty,
                                             const QList<UiObjectMember *> arrayMembers,
                                             ReadingContext *context,
                                             DifferenceHandler &differenceHandler)
1161
1162
1163
{
    QList<ModelNode> modelNodes = modelListProperty.toModelNodeList();
    int i = 0;
1164
1165
1166
    for (; i