qmljscheck.cpp 53 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Christian Kamm's avatar
Christian Kamm committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
Christian Kamm's avatar
Christian Kamm committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Christian Kamm's avatar
Christian Kamm committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
Christian Kamm's avatar
Christian Kamm committed
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
Christian Kamm's avatar
Christian Kamm committed
29
30
31

#include "qmljscheck.h"
#include "qmljsbind.h"
32
#include "qmljsevaluate.h"
33
#include "qmljsutils.h"
Christian Kamm's avatar
Christian Kamm committed
34
35
#include "parser/qmljsast_p.h"

36
37
#include <utils/qtcassert.h>

38
39
#include <QDebug>
#include <QDir>
Christian Kamm's avatar
Christian Kamm committed
40
41
42

using namespace QmlJS;
using namespace QmlJS::AST;
43
using namespace QmlJS::StaticAnalysis;
Christian Kamm's avatar
Christian Kamm committed
44

45
46
47
48
49
namespace {

class AssignmentCheck : public ValueVisitor
{
public:
50
    Message operator()(
51
            const Document::Ptr &document,
52
            const SourceLocation &location,
53
54
            const Value *lhsValue,
            const Value *rhsValue,
Christian Kamm's avatar
Christian Kamm committed
55
            Node *ast)
56
    {
57
        _doc = document;
58
        _rhsValue = rhsValue;
59
        _location = location;
Christian Kamm's avatar
Christian Kamm committed
60
61
62
63
        if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast))
            _ast = expStmt->expression;
        else
            _ast = ast->expressionCast();
64
65
66
67
68
69
70

        if (lhsValue)
            lhsValue->accept(this);

        return _message;
    }

71
72
73
74
75
    void setMessage(Type type)
    {
        _message = Message(type, _location);
    }

76
    virtual void visit(const NumberValue *value)
77
    {
Christian Kamm's avatar
Christian Kamm committed
78
        if (const QmlEnumValue *enumValue = value_cast<QmlEnumValue>(value)) {
79
            if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
80
                const QString valueName = stringLiteral->value.toString();
81

82
                if (!enumValue->keys().contains(valueName))
83
                    setMessage(ErrInvalidEnumValue);
84
            } else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
85
                       && ! _rhsValue->asUnknownValue()) {
86
                setMessage(ErrEnumValueMustBeStringOrNumber);
87
88
            }
        } else {
Christian Kamm's avatar
Christian Kamm committed
89
90
            if (cast<TrueLiteral *>(_ast)
                    || cast<FalseLiteral *>(_ast)) {
91
                setMessage(ErrNumberValueExpected);
92
            }
93
94
95
96
97
98
99
100
101
102
        }
    }

    virtual void visit(const BooleanValue *)
    {
        UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);

        if (cast<StringLiteral *>(_ast)
                || cast<NumericLiteral *>(_ast)
                || (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) {
103
            setMessage(ErrBooleanValueExpected);
104
105
106
        }
    }

107
    virtual void visit(const StringValue *value)
108
109
110
111
112
    {
        UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);

        if (cast<NumericLiteral *>(_ast)
                || (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))
Christian Kamm's avatar
Christian Kamm committed
113
114
                || cast<TrueLiteral *>(_ast)
                || cast<FalseLiteral *>(_ast)) {
115
            setMessage(ErrStringValueExpected);
116
        }
117
118
119

        if (value && value->asUrlValue()) {
            if (StringLiteral *literal = cast<StringLiteral *>(_ast)) {
120
                QUrl url(literal->value.toString());
121
                if (!url.isValid() && !url.isEmpty()) {
122
                    setMessage(ErrInvalidUrl);
123
124
125
                } else {
                    QString fileName = url.toLocalFile();
                    if (!fileName.isEmpty()) {
126
                        if (QFileInfo(fileName).isRelative()) {
127
128
129
                            fileName.prepend(QDir::separator());
                            fileName.prepend(_doc->path());
                        }
130
                        if (!QFileInfo(fileName).exists())
131
                            setMessage(WarnFileOrDirectoryDoesNotExist);
132
133
134
135
                    }
                }
            }
        }
136
137
138
139
140
    }

    virtual void visit(const ColorValue *)
    {
        if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
141
            if (!toQColor(stringLiteral->value.toString()).isValid())
142
                setMessage(ErrInvalidColor);
143
144
145
146
147
148
149
        } else {
            visit((StringValue *)0);
        }
    }

    virtual void visit(const AnchorLineValue *)
    {
150
        if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUnknownValue()))
151
            setMessage(ErrAnchorLineExpected);
152
153
    }

154
    Document::Ptr _doc;
155
156
    Message _message;
    SourceLocation _location;
157
158
159
160
    const Value *_rhsValue;
    ExpressionNode *_ast;
};

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
class ReachesEndCheck : protected Visitor
{
public:
    bool operator()(Node *node)
    {
        _labels.clear();
        _labelledBreaks.clear();
        return check(node) == ReachesEnd;
    }

protected:
    // Sorted by how much code will be reachable from that state, i.e.
    // ReachesEnd is guaranteed to reach more code than Break and so on.
    enum State
    {
        ReachesEnd = 0,
        Break = 1,
        Continue = 2,
        ReturnOrThrow = 3
    };
    State _state;
182
    QHash<QString, Node *> _labels;
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
    QSet<Node *> _labelledBreaks;

    virtual void onUnreachable(Node *)
    {}

    virtual State check(Node *node)
    {
        _state = ReachesEnd;
        Node::accept(node, this);
        return _state;
    }

    virtual bool preVisit(Node *ast)
    {
        if (ast->expressionCast())
            return false;
        if (_state == ReachesEnd)
            return true;
        if (Statement *stmt = ast->statementCast())
            onUnreachable(stmt);
        if (FunctionSourceElement *fun = cast<FunctionSourceElement *>(ast))
            onUnreachable(fun->declaration);
        if (StatementSourceElement *stmt = cast<StatementSourceElement *>(ast))
            onUnreachable(stmt->statement);
        return false;
    }

    virtual bool visit(LabelledStatement *ast)
    {
        // get the target statement
        Statement *end = ast->statement;
        forever {
            if (LabelledStatement *label = cast<LabelledStatement *>(end))
                end = label->statement;
            else
                break;
        }
220
221
        if (!ast->label.isEmpty())
            _labels[ast->label.toString()] = end;
222
223
224
225
226
227
        return true;
    }

    virtual bool visit(BreakStatement *ast)
    {
        _state = Break;
228
        if (!ast->label.isEmpty()) {
229
            if (Node *target = _labels.value(ast->label.toString())) {
230
                _labelledBreaks.insert(target);
231
232
                _state = ReturnOrThrow; // unwind until label is hit
            }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
        }
        return false;
    }

    // labelled continues don't change control flow...
    virtual bool visit(ContinueStatement *) { _state = Continue; return false; }

    virtual bool visit(ReturnStatement *) { _state = ReturnOrThrow; return false; }
    virtual bool visit(ThrowStatement *) { _state = ReturnOrThrow; return false; }

    virtual bool visit(IfStatement *ast)
    {
        State ok = check(ast->ok);
        State ko = check(ast->ko);
        _state = qMin(ok, ko);
        return false;
    }

    void handleClause(StatementList *statements, State *result, bool *fallthrough)
    {
        State clauseResult = check(statements);
        if (clauseResult == ReachesEnd) {
            *fallthrough = true;
        } else {
            *fallthrough = false;
            *result = qMin(*result, clauseResult);
        }
    }

    virtual bool visit(SwitchStatement *ast)
    {
        if (!ast->block)
            return false;
        State result = ReturnOrThrow;
        bool lastWasFallthrough = false;

        for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
            if (it->clause)
                handleClause(it->clause->statements, &result, &lastWasFallthrough);
        }
        if (ast->block->defaultClause)
            handleClause(ast->block->defaultClause->statements, &result, &lastWasFallthrough);
        for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
            if (it->clause)
                handleClause(it->clause->statements, &result, &lastWasFallthrough);
        }

280
        if (lastWasFallthrough || !ast->block->defaultClause)
281
282
283
284
285
286
287
288
289
290
            result = ReachesEnd;
        if (result == Break || _labelledBreaks.contains(ast))
            result = ReachesEnd;
        _state = result;
        return false;
    }

    virtual bool visit(TryStatement *ast)
    {
        State tryBody = check(ast->statement);
291
292
293
294
295
296
        State catchBody = ReturnOrThrow;
        if (ast->catchExpression)
            catchBody = check(ast->catchExpression->statement);
        State finallyBody = ReachesEnd;
        if (ast->finallyExpression)
            finallyBody = check(ast->finallyExpression->statement);
297
298
299
300
301

        _state = qMax(qMin(tryBody, catchBody), finallyBody);
        return false;
    }

302
    bool preconditionLoopStatement(Node *, Statement *body)
303
304
    {
        check(body);
305
        _state = ReachesEnd; // condition could be false...
306
307
308
        return false;
    }

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
    virtual bool visit(WhileStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
    virtual bool visit(ForStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
    virtual bool visit(ForEachStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
    virtual bool visit(LocalForStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
    virtual bool visit(LocalForEachStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }

    virtual bool visit(DoWhileStatement *ast)
    {
        check(ast->statement);
        // not necessarily an infinite loop due to labelled breaks
        if (_state == Continue)
            _state = ReturnOrThrow;
        if (_state == Break || _labelledBreaks.contains(ast))
            _state = ReachesEnd;
        return false;
    }
325
326
327
328
};

class MarkUnreachableCode : protected ReachesEndCheck
{
329
    QList<Message> _messages;
330
331
332
    bool _emittedWarning;

public:
333
    QList<Message> operator()(Node *ast)
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
    {
        _messages.clear();
        check(ast);
        return _messages;
    }

protected:
    virtual State check(Node *node)
    {
        bool oldwarning = _emittedWarning;
        _emittedWarning = false;
        State s = ReachesEndCheck::check(node);
        _emittedWarning = oldwarning;
        return s;
    }

    virtual void onUnreachable(Node *node)
    {
        if (_emittedWarning)
            return;
        _emittedWarning = true;

356
        Message message(WarnUnreachable, SourceLocation());
357
        if (Statement *statement = node->statementCast())
358
            message.location = locationFromRange(statement->firstSourceLocation(), statement->lastSourceLocation());
359
        else if (ExpressionNode *expr = node->expressionCast())
360
361
            message.location = locationFromRange(expr->firstSourceLocation(), expr->lastSourceLocation());
        if (message.isValid())
362
363
364
365
            _messages += message;
    }
};

366
class DeclarationsCheck : protected Visitor
367
368
{
public:
369
    QList<Message> operator()(FunctionExpression *function)
370
    {
371
        clear();
372
        for (FormalParameterList *plist = function->formals; plist; plist = plist->next) {
373
374
            if (!plist->name.isEmpty())
                _formalParameterNames += plist->name.toString();
375
376
377
378
379
380
        }

        Node::accept(function->body, this);
        return _messages;
    }

381
    QList<Message> operator()(Node *node)
382
383
    {
        clear();
384
        Node::accept(node, this);
385
386
387
        return _messages;
    }

388
protected:
389
390
391
392
393
394
395
396
397
398
    void clear()
    {
        _messages.clear();
        _declaredFunctions.clear();
        _declaredVariables.clear();
        _possiblyUndeclaredUses.clear();
        _seenNonDeclarationStatement = false;
        _formalParameterNames.clear();
    }

399
400
401
402
403
404
405
406
407
408
    void postVisit(Node *ast)
    {
        if (!_seenNonDeclarationStatement && ast->statementCast()
                && !cast<VariableStatement *>(ast)) {
            _seenNonDeclarationStatement = true;
        }
    }

    bool visit(IdentifierExpression *ast)
    {
409
        if (ast->name.isEmpty())
410
            return false;
411
        const QString &name = ast->name.toString();
412
413
414
415
416
417
418
        if (!_declaredFunctions.contains(name) && !_declaredVariables.contains(name))
            _possiblyUndeclaredUses[name].append(ast->identifierToken);
        return false;
    }

    bool visit(VariableStatement *ast)
    {
419
        if (_seenNonDeclarationStatement)
420
            addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->declarationKindToken);
421
422
423
424
425
        return true;
    }

    bool visit(VariableDeclaration *ast)
    {
426
        if (ast->name.isEmpty())
427
            return true;
428
        const QString &name = ast->name.toString();
429

430
        if (_formalParameterNames.contains(name))
431
            addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
432
        else if (_declaredFunctions.contains(name))
433
            addMessage(WarnAlreadyFunction, ast->identifierToken, name);
434
        else if (_declaredVariables.contains(name))
435
            addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
436
437

        if (_possiblyUndeclaredUses.contains(name)) {
438
439
            foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
                addMessage(WarnVarUsedBeforeDeclaration, loc, name);
440
441
442
443
444
445
446
447
448
449
            }
            _possiblyUndeclaredUses.remove(name);
        }
        _declaredVariables[name] = ast;

        return true;
    }

    bool visit(FunctionDeclaration *ast)
    {
450
        if (_seenNonDeclarationStatement)
451
            addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->functionToken);
452
453
454
455
456
457

        return visit(static_cast<FunctionExpression *>(ast));
    }

    bool visit(FunctionExpression *ast)
    {
458
        if (ast->name.isEmpty())
459
            return false;
460
        const QString &name = ast->name.toString();
461

462
        if (_formalParameterNames.contains(name))
463
            addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
464
        else if (_declaredVariables.contains(name))
465
            addMessage(WarnAlreadyVar, ast->identifierToken, name);
466
        else if (_declaredFunctions.contains(name))
467
            addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
468
469
470

        if (FunctionDeclaration *decl = cast<FunctionDeclaration *>(ast)) {
            if (_possiblyUndeclaredUses.contains(name)) {
471
472
                foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
                    addMessage(WarnFunctionUsedBeforeDeclaration, loc, name);
473
474
475
476
477
478
479
480
481
482
                }
                _possiblyUndeclaredUses.remove(name);
            }
            _declaredFunctions[name] = decl;
        }

        return false;
    }

private:
483
    void addMessage(Type type, const SourceLocation &loc, const QString &arg1 = QString())
484
    {
485
        _messages.append(Message(type, loc, arg1));
486
487
    }

488
    QList<Message> _messages;
489
490
491
492
493
494
495
    QStringList _formalParameterNames;
    QHash<QString, VariableDeclaration *> _declaredVariables;
    QHash<QString, FunctionDeclaration *> _declaredFunctions;
    QHash<QString, QList<SourceLocation> > _possiblyUndeclaredUses;
    bool _seenNonDeclarationStatement;
};

496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
class VisualAspectsPropertyBlackList : public QStringList
{
public:
   VisualAspectsPropertyBlackList()
   {
       (*this) << QLatin1String("x") << QLatin1String("y") << QLatin1String("z")
            << QLatin1String("width") << QLatin1String("height") << QLatin1String("color")
            << QLatin1String("opacity") << QLatin1String("scale")
            << QLatin1String("rotation") << QLatin1String("margins")
            << QLatin1String("verticalCenterOffset") << QLatin1String("horizontalCenterOffset")
            << QLatin1String("baselineOffset") << QLatin1String("bottomMargin")
            << QLatin1String("topMargin") << QLatin1String("leftMargin")
            << QLatin1String("rightMargin") << QLatin1String("baseline")
            << QLatin1String("centerIn") << QLatin1String("fill")
            << QLatin1String("left") << QLatin1String("right")
            << QLatin1String("mirrored") << QLatin1String("verticalCenter")
            << QLatin1String("horizontalCenter");

   }
};

class UnsupportedTypesByVisualDesigner : public QStringList
{
public:
    UnsupportedTypesByVisualDesigner()
    {
        (*this) << QLatin1String("Transform") << QLatin1String("Timer")
            << QLatin1String("Rotation") << QLatin1String("Scale")
            << QLatin1String("Translate") << QLatin1String("Package")
            << QLatin1String("Particles");
    }

};
529
530
} // end of anonymous namespace

531
532
533
Q_GLOBAL_STATIC(VisualAspectsPropertyBlackList, visualAspectsPropertyBlackList)
Q_GLOBAL_STATIC(UnsupportedTypesByVisualDesigner, unsupportedTypesByVisualDesigner)

Christian Kamm's avatar
Christian Kamm committed
534
Check::Check(Document::Ptr doc, const ContextPtr &context)
Christian Kamm's avatar
Christian Kamm committed
535
    : _doc(doc)
Christian Kamm's avatar
Christian Kamm committed
536
537
    , _context(context)
    , _scopeChain(doc, _context)
538
    , _scopeBuilder(&_scopeChain)
539
    , _importsOk(false)
540
    , _inStatementBinding(false)
541
542
543
    , _imports(0)
    , _isQtQuick2(false)

Christian Kamm's avatar
Christian Kamm committed
544
{
545
546
    _imports = context->imports(doc.data());
    if (_imports && !_imports->importFailed()) {
547
        _importsOk = true;
548
549
        _isQtQuick2 = isQtQuick2();
    }
550

551
552
553
554
    _enabledMessages = Message::allMessageTypes().toSet();
    disableMessage(HintAnonymousFunctionSpacing);
    disableMessage(HintDeclareVarsInOneLine);
    disableMessage(HintDeclarationsShouldBeAtStartOfFunction);
555
    disableMessage(HintBinaryOperatorSpacing);
556
    disableMessage(HintOneStatementPerLine);
557
    disableMessage(HintExtraParentheses);
558
559
560
561
562
    disableMessage(WarnImperativeCodeNotEditableInVisualDesigner);
    disableMessage(WarnUnsupportedTypeInVisualDesigner);
    disableMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner);
    disableMessage(WarnUndefinedValueForVisualDesigner);
    disableMessage(WarnStatesOnlyInRootItemForVisualDesigner);
Christian Kamm's avatar
Christian Kamm committed
563
564
565
566
567
568
}

Check::~Check()
{
}

569
QList<Message> Check::operator()()
Christian Kamm's avatar
Christian Kamm committed
570
571
{
    _messages.clear();
572
573
    scanCommentsForAnnotations();

Christian Kamm's avatar
Christian Kamm committed
574
    Node::accept(_doc->ast(), this);
575
576
    warnAboutUnnecessarySuppressions();

Christian Kamm's avatar
Christian Kamm committed
577
578
579
    return _messages;
}

580
581
582
583
584
585
586
587
588
589
void Check::enableMessage(Type type)
{
    _enabledMessages.insert(type);
}

void Check::disableMessage(Type type)
{
    _enabledMessages.remove(type);
}

590
591
592
593
594
595
596
597
598
599
600
bool Check::preVisit(Node *ast)
{
    _chain.append(ast);
    return true;
}

void Check::postVisit(Node *)
{
    _chain.removeLast();
}

601
bool Check::visit(UiProgram *)
Christian Kamm's avatar
Christian Kamm committed
602
603
604
605
{
    return true;
}

606
607
bool Check::visit(UiObjectInitializer *)
{
608
    QString typeName;
609
    m_propertyStack.push(StringSet());
610
611
612
613
614
615
616
    UiQualifiedId *qualifiedTypeId = qualifiedTypeNameId(parent());
    if (qualifiedTypeId) {
        typeName = qualifiedTypeId->name.toString();
        if (typeName == QLatin1String("Component"))
            m_idStack.push(StringSet());
    }

Thomas Hartmann's avatar
Thomas Hartmann committed
617
    m_typeStack.push(typeName);
618

619
620
    if (m_idStack.isEmpty())
        m_idStack.push(StringSet());
621

622
    return true;
623
624
625
626
627
}

void Check::endVisit(UiObjectInitializer *)
{
    m_propertyStack.pop();
628
    m_typeStack.pop();
629
    UiObjectDefinition *objectDenition = cast<UiObjectDefinition *>(parent());
630
    if (objectDenition && objectDenition->qualifiedTypeNameId->name == QLatin1String("Component"))
631
632
        m_idStack.pop();
    UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
633
    if (objectBinding && objectBinding->qualifiedTypeNameId->name == QLatin1String("Component"))
634
        m_idStack.pop();
635
636
637
638
}

void Check::checkProperty(UiQualifiedId *qualifiedId)
{
639
    const QString id = toString(qualifiedId);
640
    if (id.at(0).isLower()) {
641
        if (m_propertyStack.top().contains(id))
642
            addMessage(ErrPropertiesCanOnlyHaveOneBinding, fullLocationForQualifiedId(qualifiedId));
643
644
645
646
        m_propertyStack.top().insert(id);
    }
}

Christian Kamm's avatar
Christian Kamm committed
647
648
bool Check::visit(UiObjectDefinition *ast)
{
649
    visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
Christian Kamm's avatar
Christian Kamm committed
650
651
652
653
654
655
    return false;
}

bool Check::visit(UiObjectBinding *ast)
{
    checkScopeObjectMember(ast->qualifiedId);
656
657
    if (!ast->hasOnToken)
        checkProperty(ast->qualifiedId);
Christian Kamm's avatar
Christian Kamm committed
658

659
    visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
660
661
662
    return false;
}

663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
static bool expressionAffectsVisualAspects(BinaryExpression *expression)
{
    if (expression->op == QSOperator::Assign
            || expression->op == QSOperator::InplaceSub
            || expression->op == QSOperator::InplaceAdd
            || expression->op == QSOperator::InplaceDiv
            || expression->op == QSOperator::InplaceMul
            || expression->op == QSOperator::InplaceOr
            || expression->op == QSOperator::InplaceXor
            || expression->op == QSOperator::InplaceAnd) {

        const ExpressionNode *lhsValue = expression->left;

        if (const IdentifierExpression* identifierExpression = cast<const IdentifierExpression *>(lhsValue)) {
            if (visualAspectsPropertyBlackList()->contains(identifierExpression->name.toString()))
                return true;
        } else if (const FieldMemberExpression* fieldMemberExpression = cast<const FieldMemberExpression *>(lhsValue)) {
            if (visualAspectsPropertyBlackList()->contains(fieldMemberExpression->name.toString()))
                return true;
        }
    }
    return false;
}

static UiQualifiedId *getRightMostIdentifier(UiQualifiedId *typeId)
{
        if (typeId->next)
            return getRightMostIdentifier(typeId->next);

        return typeId;
}

static bool checkTypeForDesignerSupport(UiQualifiedId *typeId)
{
    return unsupportedTypesByVisualDesigner()->contains(getRightMostIdentifier(typeId)->name.toString());
}

static bool checkTopLevelBindingForParentReference(ExpressionStatement *expStmt, const QString &source)
{
    if (!expStmt)
        return false;

    SourceLocation location = locationFromRange(expStmt->firstSourceLocation(), expStmt->lastSourceLocation());
    QString stmtSource = source.mid(location.begin(), location.length);

708
    if (stmtSource.contains(QRegExp(QLatin1String("(^|\\W)parent\\."))))
709
710
711
712
713
        return true;

    return false;
}

714
715
void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
                           UiObjectInitializer *initializer)
716
{
717
718
719
    // Don't do type checks if it's a grouped property binding.
    // For instance: anchors { ... }
    if (_doc->bind()->isGroupedPropertyBinding(ast)) {
720
721
722
723
724
        checkScopeObjectMember(typeId);
        // ### don't give up!
        return;
    }

725
726
727
728
729
730
731
732
    const SourceLocation typeErrorLocation = fullLocationForQualifiedId(typeId);

    if (checkTypeForDesignerSupport(typeId))
        addMessage(WarnUnsupportedTypeInVisualDesigner, typeErrorLocation);

    if (m_typeStack.count() > 1 && getRightMostIdentifier(typeId)->name.toString() == QLatin1String("State"))
        addMessage(WarnStatesOnlyInRootItemForVisualDesigner, typeErrorLocation);

733
    bool typeError = false;
734
735
736
    if (_importsOk) {
        const ObjectValue *prototype = _context->lookupType(_doc.data(), typeId);
        if (!prototype) {
737
            typeError = true;
738
739
740
741
742
743
744
            addMessage(ErrUnknownComponent, typeErrorLocation);
        } else {
            PrototypeIterator iter(prototype, _context);
            QList<const ObjectValue *> prototypes = iter.all();
            if (iter.error() != PrototypeIterator::NoError)
                typeError = true;
            const ObjectValue *lastPrototype = prototypes.last();
745
746
747
748
749
750
751
            foreach (const ObjectValue *objectValue, prototypes) {
                if (objectValue->className() == QLatin1String("QGraphicsObject")
                        && _isQtQuick2) {
                    addMessage(WarnAboutQtQuick1InsteadQtQuick2, typeErrorLocation);
                }
            }

752
753
            if (iter.error() == PrototypeIterator::ReferenceResolutionError) {
                if (const QmlPrototypeReference *ref =
Christian Kamm's avatar
Christian Kamm committed
754
                        value_cast<QmlPrototypeReference>(lastPrototype->prototype())) {
755
756
757
758
759
760
761
762
                    addMessage(ErrCouldNotResolvePrototypeOf, typeErrorLocation,
                               toString(ref->qmlTypeName()), lastPrototype->className());
                } else {
                    addMessage(ErrCouldNotResolvePrototype, typeErrorLocation,
                               lastPrototype->className());
                }
            } else if (iter.error() == PrototypeIterator::CycleError) {
                addMessage(ErrPrototypeCycle, typeErrorLocation,
763
                           lastPrototype->className());
764
765
766
767
768
769
770
            }
        }
    }

    _scopeBuilder.push(ast);

    if (typeError) {
771
772
773
        // suppress subsequent errors about scope object lookup by clearing
        // the scope object list
        // ### todo: better way?
774
        _scopeChain.setQmlScopeObjects(QList<const ObjectValue *>());
775
776
    }

777
    Node::accept(initializer, this);
Christian Kamm's avatar
Christian Kamm committed
778

779
    _scopeBuilder.pop();
Christian Kamm's avatar
Christian Kamm committed
780
781
782
783
}

bool Check::visit(UiScriptBinding *ast)
{
784
    // special case for id property
785
    if (ast->qualifiedId->name == QLatin1String("id") && ! ast->qualifiedId->next) {
786
787
788
789
790
791
792
793
        if (! ast->statement)
            return false;

        const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
                                                     ast->statement->lastSourceLocation());

        ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
        if (!expStmt) {
794
            addMessage(ErrIdExpected, loc);
795
796
797
            return false;
        }

798
799
        QString id;
        if (IdentifierExpression *idExp = cast<IdentifierExpression *>(expStmt->expression)) {
800
            id = idExp->name.toString();
801
        } else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) {
802
            id = strExp->value.toString();
803
            addMessage(ErrInvalidId, loc);
804
        } else {
805
            addMessage(ErrIdExpected, loc);
806
807
808
            return false;
        }

809
        if (id.isEmpty() || (!id.at(0).isLower() && id.at(0) != QLatin1Char('_'))) {
810
            addMessage(ErrInvalidId, loc);
811
812
            return false;
        }
813

814
        if (m_idStack.top().contains(id)) {
815
            addMessage(ErrDuplicateId, loc);
816
817
            return false;
        }
818
        m_idStack.top().insert(id);
819
820
    }

821
822
823
824
825
826
827
    if (m_typeStack.count() == 1
            && visualAspectsPropertyBlackList()->contains(ast->qualifiedId->name.toString())
            && checkTopLevelBindingForParentReference(cast<ExpressionStatement *>(ast->statement), _doc->source())) {
        addMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner,
                   locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
    }

828
829
    checkProperty(ast->qualifiedId);

Christian Kamm's avatar
Christian Kamm committed
830
831
832
    if (!ast->statement)
        return false;

833
834
    const Value *lhsValue = checkScopeObjectMember(ast->qualifiedId);
    if (lhsValue) {
Christian Kamm's avatar
Christian Kamm committed
835
836
        Evaluate evaluator(&_scopeChain);
        const Value *rhsValue = evaluator(ast->statement);
837

838
839
840
841
842
843
        if (visualAspectsPropertyBlackList()->contains(ast->qualifiedId->name.toString()) &&
                rhsValue->asUndefinedValue()) {
            addMessage(WarnUndefinedValueForVisualDesigner,
                       locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
        }

Christian Kamm's avatar
Christian Kamm committed
844
845
846
        const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
                                                     ast->statement->lastSourceLocation());
        AssignmentCheck assignmentCheck;
847
848
849
        Message message = assignmentCheck(_doc, loc, lhsValue, rhsValue, ast->statement);
        if (message.isValid())
            addMessage(message);
850
    }
Christian Kamm's avatar
Christian Kamm committed
851

852
    checkBindingRhs(ast->statement);
853

854
855
    Node::accept(ast->qualifiedId, this);
    _scopeBuilder.push(ast);
856
    _inStatementBinding = true;
857
    Node::accept(ast->statement, this);
858
    _inStatementBinding = false;
859
860
861
    _scopeBuilder.pop();

    return false;
Christian Kamm's avatar
Christian Kamm committed
862
863
864
865
866
}

bool Check::visit(UiArrayBinding *ast)
{
    checkScopeObjectMember(ast->qualifiedId);
867
    checkProperty(ast->qualifiedId);
Christian Kamm's avatar
Christian Kamm committed
868
869
870
871

    return true;
}

872
873
bool Check::visit(UiPublicMember *ast)
{
874
875
876
    if (ast->type == UiPublicMember::Property) {
        // check if the member type is valid
        if (!ast->memberType.isEmpty()) {
877
            const QStringRef name = ast->memberType;
878
            if (!name.isEmpty() && name.at(0).isLower()) {
879
880
881
                const QString nameS = name.toString();
                if (!isValidBuiltinPropertyType(nameS))
                    addMessage(ErrInvalidPropertyType, ast->typeToken, nameS);
882
883
884
885
886
887
            }

            // warn about dubious use of var/variant
            if (name == QLatin1String("variant") || name == QLatin1String("var")) {
                Evaluate evaluator(&_scopeChain);
                const Value *init = evaluator(ast->statement);
888
                QString preferredType;
889
                if (init->asNumberValue())
890
                    preferredType = tr("'int' or 'real'");
891
                else if (init->asStringValue())
892
                    preferredType = QLatin1String("'string'");
893
                else if (init->asBooleanValue())
894
                    preferredType = QLatin1String("'bool'");
895
                else if (init->asColorValue())
896
                    preferredType = QLatin1String("'color'");
897
                else if (init == _context->valueOwner()->qmlPointObject())
898
                    preferredType = QLatin1String("'point'");
899
                else if (init == _context->valueOwner()->qmlRectObject())
900
                    preferredType = QLatin1String("'rect'");
901
                else if (init == _context->valueOwner()->qmlSizeObject())
902
                    preferredType = QLatin1String("'size'");
903
                else if (init == _context->valueOwner()->qmlVector2DObject())
904
                    preferredType = QLatin1String("'vector2d'");
905
                else if (init == _context->valueOwner()->qmlVector3DObject())
906
                    preferredType = QLatin1String("'vector3d'");
907
                else if (init == _context->valueOwner()->qmlVector4DObject())
908
                    preferredType = QLatin1String("'vector4d'");
909
                else if (init == _context->valueOwner()->qmlQuaternionObject())
910
                    preferredType = QLatin1String("'quaternion'");
911
                else if (init == _context->valueOwner()->qmlMatrix4x4Object())
912
                    preferredType = QLatin1String("'matrix4x4'");
913

914
915
                if (!preferredType.isEmpty())
                    addMessage(HintPreferNonVarPropertyType, ast->typeToken, preferredType);
916
            }
917
        }
918

919
        checkBindingRhs(ast->statement);
920

921
        _scopeBuilder.push(ast);
922
        _inStatementBinding = true;
923
        Node::accept(ast->statement, this);
924
        _inStatementBinding = false;
925
926
927
        Node::accept(ast->binding, this);
        _scopeBuilder.pop();
    }
928
929

    return false;
930
931
}

932
bool Check::visit(IdentifierExpression *)
933
934
935
936
{
    // currently disabled: too many false negatives
    return true;

937
938
939
940
941
942
//    _lastValue = 0;
//    if (!ast->name.isEmpty()) {
//        Evaluate evaluator(&_scopeChain);
//        _lastValue = evaluator.reference(ast);
//        if (!_lastValue)
//            addMessage(ErrUnknownIdentifier, ast->identifierToken);
Christian Kamm's avatar
Christian Kamm committed
943
//        if (const Reference *ref = value_cast<Reference>(_lastValue)) {
944
945
946
947
948
949
//            _lastValue = _context->lookupReference(ref);
//            if (!_lastValue)
//                error(ast->identifierToken, tr("could not resolve"));
//        }
//    }
//    return false;
950
951
}

952
bool Check::visit(FieldMemberExpression *)
953
954
955
956
{
    // currently disabled: too many false negatives
    return true;

957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
//    Node::accept(ast->base, this);
//    if (!_lastValue)
//        return false;
//    const ObjectValue *obj = _lastValue->asObjectValue();
//    if (!obj) {
//        error(locationFromRange(ast->base->firstSourceLocation(), ast->base->lastSourceLocation()),
//              tr("does not have members"));
//    }
//    if (!obj || ast->name.isEmpty()) {
//        _lastValue = 0;
//        return false;
//    }
//    _lastValue = obj->lookupMember(ast->name.toString(), _context);
//    if (!_lastValue)
//        error(ast->identifierToken, tr("unknown member"));
//    return false;
973
974
975
976
977
978
979
980
981
}

bool Check::visit(FunctionDeclaration *ast)
{
    return visit(static_cast<FunctionExpression *>(ast));
}

bool Check::visit(FunctionExpression *ast)
{
982
983
984
985
986
987
988
989
990
991
    if (ast->name.isEmpty()) {
        SourceLocation locfunc = ast->functionToken;
        SourceLocation loclparen = ast->lparenToken;
        if (locfunc.isValid() && loclparen.isValid()
                && (locfunc.startLine != loclparen.startLine
                    || locfunc.end() + 1 != loclparen.begin())) {
            addMessage(HintAnonymousFunctionSpacing, locationFromRange(locfunc, loclparen));
        }
    }

992
    DeclarationsCheck bodyCheck;
993
994
995
996
    addMessages(bodyCheck(ast));

    MarkUnreachableCode unreachableCheck;
    addMessages(unreachableCheck(ast->body));
997

998
    Node::accept(ast->formals, this);
999
1000
1001

    const bool wasInStatementBinding = _inStatementBinding;
    _inStatementBinding = false;
1002
1003
1004
    _scopeBuilder.push(ast);
    Node::accept(ast->body, this);
    _scopeBuilder.pop();
1005
1006
    _inStatementBinding = wasInStatementBinding;

1007
1008
1009
    return false;
}

1010
static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs)
1011
{
1012
1013
    if (lhs->asUnknownValue() || rhs->asUnknownValue())
        return true; // may coerce or not
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023

    if (lhs->asStringValue() && rhs->asNumberValue())
        return true; // coerces string to number

    if (lhs->asObjectValue() && rhs->asNumberValue())
        return true; // coerces object to primitive

    if (lhs->asObjectValue() && rhs->asStringValue())
        return true; // coerces object to primitive

1024
1025
    if (lhs->asBooleanValue() && (!rhs->asBooleanValue()
                                  && !rhs->asUndefinedValue()))
1026
1027
        return true; // coerces bool to number

1028
1029
1030
1031
1032
    return false;
}

bool Check::visit(BinaryExpression *ast)
{
1033
1034
1035
1036
1037
1038
1039
1040
1041
    const QString source = _doc->source();

    // check spacing
    SourceLocation op = ast->operatorToken;
    if ((op.begin() > 0 && !source.at(op.begin() - 1).isSpace())
            || (int(op.end()) < source.size() && !source.at(op.end()).isSpace())) {
        addMessage(HintBinaryOperatorSpacing, op);
    }

1042
1043
1044
1045
1046
    SourceLocation expressionSourceLocation = locationFromRange(ast->firstSourceLocation(),
                                                                ast->lastSourceLocation());
    if (expressionAffectsVisualAspects(ast))
        addMessage(WarnImperativeCodeNotEditableInVisualDesigner, expressionSourceLocation);

1047
    // check ==, !=
1048
    if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) {
1049
        Evaluate eval(&_scopeChain);
1050
1051
1052
1053
        const Value *lhsValue = eval(ast->left);
        const Value *rhsValue = eval(ast->right);
        if (shouldAvoidNonStrictEqualityCheck(lhsValue, rhsValue)
                || shouldAvoidNonStrictEqualityCheck(rhsValue, lhsValue)) {
1054
            addMessage(MaybeWarnEqualityTypeCoercion, ast->operatorToken);
1055
1056
        }
    }
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085

    // check odd + ++ combinations
    const QLatin1Char newline('\n');
    if (ast->op == QSOperator::Add || ast->op == QSOperator::Sub) {
        QChar match;
        Type msg;
        if (ast->op == QSOperator::Add) {
            match = QLatin1Char('+');
            msg = WarnConfusingPluses;
        } else {
            QTC_CHECK(ast->op == QSOperator::Sub);
            match = QLatin1Char('-');
            msg = WarnConfusingMinuses;
        }

        if (int(op.end()) + 1 < source.size()) {
            const QChar next = source.at(op.end());
            if (next.isSpace() && next != newline
                    && source.at(op.end() + 1) == match)
                addMessage(msg, SourceLocation(op.begin(), 3, op.startLine, op.startColumn));
        }
        if (op.begin() >= 2) {
            const QChar prev = source.at(op.begin() - 1);
            if (prev.isSpace() && prev != newline
                    && source.at(op.begin() - 2) == match)
                addMessage(msg, SourceLocation(op.begin() - 2, 3, op.startLine, op.startColumn - 2));
        }
    }

1086
1087
1088
1089
1090
1091
    return true;
}

bool Check::visit(Block *ast)
{
    if (Node *p = parent()) {
1092
        if (!cast<UiScriptBinding *>(p)
1093
                && !cast<UiPublicMember *>(p)
1094
1095
1096
1097
1098
                && !cast<TryStatement *>(p)
                && !cast<Catch *>(p)
                && !cast<Finally *>(p)
                && !cast<ForStatement *>(p)
                && !cast<ForEachStatement *>(p)
1099
1100
                && !cast<LocalForStatement *>(p)
                && !cast<LocalForEachStatement *>(p)
1101
1102
1103
1104
1105
                && !cast<DoWhileStatement *>(p)
                && !cast<WhileStatement *>(p)
                && !cast<IfStatement *>(p)
                && !cast<SwitchStatement *>(p)
                && !cast<WithStatement *>(p)) {
1106
            addMessage(WarnBlock, ast->lbraceToken);
1107
        }
1108
        if (!ast->statements
1109
                && cast<