2010-02-16 10:36:09 +01:00
|
|
|
/**************************************************************************
|
|
|
|
**
|
|
|
|
** This file is part of Qt Creator
|
|
|
|
**
|
2011-01-11 16:28:15 +01:00
|
|
|
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
2011-04-13 08:42:33 +02:00
|
|
|
** Contact: Nokia Corporation (info@qt.nokia.com)
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
|
|
|
**
|
|
|
|
** GNU Lesser General Public License Usage
|
|
|
|
**
|
2011-04-13 08:42:33 +02:00
|
|
|
** 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.
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
2010-12-17 16:01:08 +01:00
|
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
2011-04-13 08:42:33 +02:00
|
|
|
** rights. These rights are described in the Nokia Qt LGPL Exception
|
2010-12-17 16:01:08 +01:00
|
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
|
**
|
2011-04-13 08:42:33 +02:00
|
|
|
** 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.
|
|
|
|
**
|
2010-12-17 16:01:08 +01:00
|
|
|
** If you have questions regarding the use of this file, please contact
|
2011-05-06 15:05:37 +02:00
|
|
|
** Nokia at info@qt.nokia.com.
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
|
|
|
**************************************************************************/
|
|
|
|
|
|
|
|
#include "qmljscheck.h"
|
|
|
|
#include "qmljsbind.h"
|
2011-07-01 13:51:53 +02:00
|
|
|
#include "qmljscontext.h"
|
2010-02-16 11:53:21 +01:00
|
|
|
#include "qmljsevaluate.h"
|
2010-02-16 10:36:09 +01:00
|
|
|
#include "parser/qmljsast_p.h"
|
|
|
|
|
|
|
|
#include <QtCore/QDebug>
|
2010-11-23 14:30:23 +01:00
|
|
|
#include <QtCore/QDir>
|
2010-02-23 14:36:38 +01:00
|
|
|
#include <QtGui/QColor>
|
2010-02-23 12:36:12 +01:00
|
|
|
#include <QtGui/QApplication>
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
using namespace QmlJS;
|
|
|
|
using namespace QmlJS::AST;
|
|
|
|
|
2010-07-29 11:20:06 +02:00
|
|
|
QColor QmlJS::toQColor(const QString &qmlColorString)
|
|
|
|
{
|
|
|
|
QColor color;
|
|
|
|
if (qmlColorString.size() == 9 && qmlColorString.at(0) == QLatin1Char('#')) {
|
|
|
|
bool ok;
|
|
|
|
const int alpha = qmlColorString.mid(1, 2).toInt(&ok, 16);
|
|
|
|
if (ok) {
|
|
|
|
QString name(qmlColorString.at(0));
|
|
|
|
name.append(qmlColorString.right(6));
|
|
|
|
if (QColor::isValidColor(name)) {
|
|
|
|
color.setNamedColor(name);
|
|
|
|
color.setAlpha(alpha);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (QColor::isValidColor(qmlColorString))
|
|
|
|
color.setNamedColor(qmlColorString);
|
|
|
|
}
|
|
|
|
return color;
|
|
|
|
}
|
2010-02-23 17:02:50 +01:00
|
|
|
|
2010-09-16 15:29:37 +02:00
|
|
|
SourceLocation QmlJS::locationFromRange(const SourceLocation &start,
|
|
|
|
const SourceLocation &end)
|
|
|
|
{
|
|
|
|
return SourceLocation(start.offset,
|
|
|
|
end.end() - start.begin(),
|
|
|
|
start.startLine,
|
|
|
|
start.startColumn);
|
|
|
|
}
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
SourceLocation QmlJS::fullLocationForQualifiedId(AST::UiQualifiedId *qualifiedId)
|
|
|
|
{
|
|
|
|
SourceLocation start = qualifiedId->identifierToken;
|
|
|
|
SourceLocation end = qualifiedId->identifierToken;
|
|
|
|
|
|
|
|
for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (iter->identifierToken.isValid())
|
2010-12-07 17:31:53 +01:00
|
|
|
end = iter->identifierToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
return locationFromRange(start, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-09-16 15:29:37 +02:00
|
|
|
DiagnosticMessage QmlJS::errorMessage(const AST::SourceLocation &loc, const QString &message)
|
|
|
|
{
|
|
|
|
return DiagnosticMessage(DiagnosticMessage::Error, loc, message);
|
|
|
|
}
|
|
|
|
|
2011-09-05 15:37:14 +02:00
|
|
|
namespace {
|
|
|
|
class SharedData
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
SharedData()
|
|
|
|
{
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("action"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("bool"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("color"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("date"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("double"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("enumeration"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("font"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("int"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("list"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("point"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("real"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("rect"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("size"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("string"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("time"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("url"));
|
2011-10-06 09:53:08 +02:00
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("var"));
|
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("variant")); // obsolete in Qt 5
|
2011-09-05 15:37:14 +02:00
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("vector3d"));
|
2011-09-08 08:55:20 +02:00
|
|
|
validBuiltinPropertyNames.insert(QLatin1String("alias"));
|
2011-09-05 15:37:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QSet<QString> validBuiltinPropertyNames;
|
|
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
Q_GLOBAL_STATIC(SharedData, sharedData)
|
|
|
|
|
|
|
|
bool QmlJS::isValidBuiltinPropertyType(const QString &name)
|
|
|
|
{
|
|
|
|
return sharedData()->validBuiltinPropertyNames.contains(name);
|
|
|
|
}
|
|
|
|
|
2010-02-23 17:02:50 +01:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
class AssignmentCheck : public ValueVisitor
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DiagnosticMessage operator()(
|
2010-11-23 14:30:23 +01:00
|
|
|
const Document::Ptr &document,
|
2010-02-23 17:02:50 +01:00
|
|
|
const SourceLocation &location,
|
2011-08-08 12:47:49 +02:00
|
|
|
const Value *lhsValue,
|
|
|
|
const Value *rhsValue,
|
2011-09-01 15:04:21 +02:00
|
|
|
Node *ast)
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
2010-11-23 14:30:23 +01:00
|
|
|
_doc = document;
|
2010-02-23 17:02:50 +01:00
|
|
|
_message = DiagnosticMessage(DiagnosticMessage::Error, location, QString());
|
|
|
|
_rhsValue = rhsValue;
|
2011-09-01 15:04:21 +02:00
|
|
|
if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast))
|
|
|
|
_ast = expStmt->expression;
|
|
|
|
else
|
|
|
|
_ast = ast->expressionCast();
|
2010-02-23 17:02:50 +01:00
|
|
|
|
|
|
|
if (lhsValue)
|
|
|
|
lhsValue->accept(this);
|
|
|
|
|
|
|
|
return _message;
|
|
|
|
}
|
|
|
|
|
2010-05-19 12:23:55 +02:00
|
|
|
virtual void visit(const NumberValue *value)
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
2010-05-19 12:23:55 +02:00
|
|
|
if (const QmlEnumValue *enumValue = dynamic_cast<const QmlEnumValue *>(value)) {
|
|
|
|
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString valueName = stringLiteral->value.toString();
|
2010-05-19 12:23:55 +02:00
|
|
|
|
|
|
|
if (!enumValue->keys().contains(valueName)) {
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("unknown value for enum");
|
2010-05-19 12:23:55 +02:00
|
|
|
}
|
2010-11-29 08:59:54 +01:00
|
|
|
} else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
|
|
|
|
&& ! _rhsValue->asUndefinedValue()) {
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("enum value is not a string or number");
|
2010-05-19 12:23:55 +02:00
|
|
|
}
|
|
|
|
} else {
|
2011-09-01 15:04:21 +02:00
|
|
|
if (cast<TrueLiteral *>(_ast)
|
|
|
|
|| cast<FalseLiteral *>(_ast)) {
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("numerical value expected");
|
2010-05-19 12:23:55 +02:00
|
|
|
}
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void visit(const BooleanValue *)
|
|
|
|
{
|
|
|
|
UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
|
|
|
|
|
|
|
|
if (cast<StringLiteral *>(_ast)
|
|
|
|
|| cast<NumericLiteral *>(_ast)
|
|
|
|
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) {
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("boolean value expected");
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-23 14:30:23 +01:00
|
|
|
virtual void visit(const StringValue *value)
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
|
|
|
UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
|
|
|
|
|
|
|
|
if (cast<NumericLiteral *>(_ast)
|
|
|
|
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))
|
2011-09-01 15:04:21 +02:00
|
|
|
|| cast<TrueLiteral *>(_ast)
|
|
|
|
|| cast<FalseLiteral *>(_ast)) {
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("string value expected");
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
2010-11-23 14:30:23 +01:00
|
|
|
|
|
|
|
if (value && value->asUrlValue()) {
|
|
|
|
if (StringLiteral *literal = cast<StringLiteral *>(_ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
QUrl url(literal->value.toString());
|
2010-11-23 14:30:23 +01:00
|
|
|
if (!url.isValid() && !url.isEmpty()) {
|
|
|
|
_message.message = Check::tr("not a valid url");
|
|
|
|
} else {
|
|
|
|
QString fileName = url.toLocalFile();
|
|
|
|
if (!fileName.isEmpty()) {
|
2011-02-08 14:38:48 +01:00
|
|
|
if (QFileInfo(fileName).isRelative()) {
|
2010-11-23 14:30:23 +01:00
|
|
|
fileName.prepend(QDir::separator());
|
|
|
|
fileName.prepend(_doc->path());
|
|
|
|
}
|
2011-03-01 14:33:40 +01:00
|
|
|
if (!QFileInfo(fileName).exists()) {
|
2010-11-23 14:30:23 +01:00
|
|
|
_message.message = Check::tr("file or directory does not exist");
|
2011-03-01 14:33:40 +01:00
|
|
|
_message.kind = DiagnosticMessage::Warning;
|
|
|
|
}
|
2010-11-23 14:30:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void visit(const ColorValue *)
|
|
|
|
{
|
|
|
|
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!toQColor(stringLiteral->value.toString()).isValid())
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("not a valid color");
|
2010-02-23 17:02:50 +01:00
|
|
|
} else {
|
|
|
|
visit((StringValue *)0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void visit(const AnchorLineValue *)
|
|
|
|
{
|
|
|
|
if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUndefinedValue()))
|
2010-05-19 13:32:11 +02:00
|
|
|
_message.message = Check::tr("expected anchor line");
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
|
2010-11-23 14:30:23 +01:00
|
|
|
Document::Ptr _doc;
|
2010-02-23 17:02:50 +01:00
|
|
|
DiagnosticMessage _message;
|
|
|
|
const Value *_rhsValue;
|
|
|
|
ExpressionNode *_ast;
|
|
|
|
};
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
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;
|
2011-09-16 10:35:48 +02:00
|
|
|
QHash<QString, Node *> _labels;
|
2011-09-07 07:21:38 +02:00
|
|
|
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;
|
|
|
|
}
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!ast->label.isEmpty())
|
|
|
|
_labels[ast->label.toString()] = end;
|
2011-09-07 07:21:38 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool visit(BreakStatement *ast)
|
|
|
|
{
|
|
|
|
_state = Break;
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!ast->label.isEmpty()) {
|
2011-09-19 15:28:05 +02:00
|
|
|
if (Node *target = _labels.value(ast->label.toString())) {
|
2011-09-07 07:21:38 +02:00
|
|
|
_labelledBreaks.insert(target);
|
2011-09-19 15:28:05 +02:00
|
|
|
_state = ReturnOrThrow; // unwind until label is hit
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-09-19 15:28:05 +02:00
|
|
|
if (lastWasFallthrough || !ast->block->defaultClause)
|
2011-09-07 07:21:38 +02:00
|
|
|
result = ReachesEnd;
|
|
|
|
if (result == Break || _labelledBreaks.contains(ast))
|
|
|
|
result = ReachesEnd;
|
|
|
|
_state = result;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool visit(TryStatement *ast)
|
|
|
|
{
|
|
|
|
State tryBody = check(ast->statement);
|
2011-09-16 15:32:47 +02:00
|
|
|
State catchBody = ReturnOrThrow;
|
|
|
|
if (ast->catchExpression)
|
|
|
|
catchBody = check(ast->catchExpression->statement);
|
|
|
|
State finallyBody = ReachesEnd;
|
|
|
|
if (ast->finallyExpression)
|
|
|
|
finallyBody = check(ast->finallyExpression->statement);
|
2011-09-07 07:21:38 +02:00
|
|
|
|
|
|
|
_state = qMax(qMin(tryBody, catchBody), finallyBody);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-19 15:28:05 +02:00
|
|
|
bool preconditionLoopStatement(Node *, Statement *body)
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
check(body);
|
2011-09-19 15:28:05 +02:00
|
|
|
_state = ReachesEnd; // condition could be false...
|
2011-09-07 07:21:38 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-19 15:28:05 +02:00
|
|
|
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;
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
class MarkUnreachableCode : protected ReachesEndCheck
|
|
|
|
{
|
|
|
|
QList<DiagnosticMessage> _messages;
|
|
|
|
bool _emittedWarning;
|
|
|
|
|
|
|
|
public:
|
|
|
|
QList<DiagnosticMessage> operator()(Node *ast)
|
|
|
|
{
|
|
|
|
_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;
|
|
|
|
|
|
|
|
DiagnosticMessage message(DiagnosticMessage::Warning, SourceLocation(), Check::tr("unreachable"));
|
|
|
|
if (Statement *statement = node->statementCast())
|
|
|
|
message.loc = locationFromRange(statement->firstSourceLocation(), statement->lastSourceLocation());
|
|
|
|
else if (ExpressionNode *expr = node->expressionCast())
|
|
|
|
message.loc = locationFromRange(expr->firstSourceLocation(), expr->lastSourceLocation());
|
|
|
|
if (message.loc.isValid())
|
|
|
|
_messages += message;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-09-06 13:59:50 +02:00
|
|
|
class DeclarationsCheck : protected Visitor
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
QList<DiagnosticMessage> operator()(FunctionExpression *function, Check::Options options)
|
|
|
|
{
|
2010-12-06 10:03:53 +01:00
|
|
|
clear();
|
2010-11-25 13:38:15 +01:00
|
|
|
_options = options;
|
|
|
|
for (FormalParameterList *plist = function->formals; plist; plist = plist->next) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!plist->name.isEmpty())
|
|
|
|
_formalParameterNames += plist->name.toString();
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Node::accept(function->body, this);
|
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
QList<DiagnosticMessage> operator()(Node *node, Check::Options options)
|
2010-12-06 10:03:53 +01:00
|
|
|
{
|
|
|
|
clear();
|
|
|
|
_options = options;
|
2011-09-07 07:21:38 +02:00
|
|
|
Node::accept(node, this);
|
2010-12-06 10:03:53 +01:00
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
protected:
|
2010-12-06 10:03:53 +01:00
|
|
|
void clear()
|
|
|
|
{
|
|
|
|
_messages.clear();
|
|
|
|
_declaredFunctions.clear();
|
|
|
|
_declaredVariables.clear();
|
|
|
|
_possiblyUndeclaredUses.clear();
|
|
|
|
_seenNonDeclarationStatement = false;
|
|
|
|
_formalParameterNames.clear();
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
void postVisit(Node *ast)
|
|
|
|
{
|
|
|
|
if (!_seenNonDeclarationStatement && ast->statementCast()
|
|
|
|
&& !cast<VariableStatement *>(ast)) {
|
|
|
|
_seenNonDeclarationStatement = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(IdentifierExpression *ast)
|
|
|
|
{
|
2011-09-13 09:57:24 +02:00
|
|
|
if (ast->name.isEmpty())
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString &name = ast->name.toString();
|
2010-11-25 13:38:15 +01:00
|
|
|
if (!_declaredFunctions.contains(name) && !_declaredVariables.contains(name))
|
|
|
|
_possiblyUndeclaredUses[name].append(ast->identifierToken);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(VariableStatement *ast)
|
|
|
|
{
|
|
|
|
if (_options & Check::WarnDeclarationsNotStartOfFunction && _seenNonDeclarationStatement) {
|
|
|
|
warning(ast->declarationKindToken, Check::tr("declarations should be at the start of a function"));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(VariableDeclaration *ast)
|
|
|
|
{
|
2011-09-13 09:57:24 +02:00
|
|
|
if (ast->name.isEmpty())
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString &name = ast->name.toString();
|
2010-11-25 13:38:15 +01:00
|
|
|
|
2011-09-06 13:59:50 +02:00
|
|
|
if (_options & Check::WarnDuplicateDeclaration) {
|
|
|
|
if (_formalParameterNames.contains(name)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->identifierToken, Check::tr("already a formal parameter"));
|
2011-09-06 13:59:50 +02:00
|
|
|
} else if (_declaredFunctions.contains(name)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->identifierToken, Check::tr("already declared as function"));
|
2011-09-06 13:59:50 +02:00
|
|
|
} else if (_declaredVariables.contains(name)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->identifierToken, Check::tr("duplicate declaration"));
|
2011-09-06 13:59:50 +02:00
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (_possiblyUndeclaredUses.contains(name)) {
|
|
|
|
if (_options & Check::WarnUseBeforeDeclaration) {
|
|
|
|
foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
|
|
|
|
warning(loc, Check::tr("variable is used before being declared"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_possiblyUndeclaredUses.remove(name);
|
|
|
|
}
|
|
|
|
_declaredVariables[name] = ast;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(FunctionDeclaration *ast)
|
|
|
|
{
|
|
|
|
if (_options & Check::WarnDeclarationsNotStartOfFunction &&_seenNonDeclarationStatement) {
|
|
|
|
warning(ast->functionToken, Check::tr("declarations should be at the start of a function"));
|
|
|
|
}
|
|
|
|
|
|
|
|
return visit(static_cast<FunctionExpression *>(ast));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(FunctionExpression *ast)
|
|
|
|
{
|
2011-09-13 09:57:24 +02:00
|
|
|
if (ast->name.isEmpty())
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString &name = ast->name.toString();
|
2010-11-25 13:38:15 +01:00
|
|
|
|
2011-09-06 13:59:50 +02:00
|
|
|
if (_options & Check::WarnDuplicateDeclaration) {
|
|
|
|
if (_formalParameterNames.contains(name)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->identifierToken, Check::tr("already a formal parameter"));
|
2011-09-06 13:59:50 +02:00
|
|
|
} else if (_declaredVariables.contains(name)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->identifierToken, Check::tr("already declared as var"));
|
2011-09-06 13:59:50 +02:00
|
|
|
} else if (_declaredFunctions.contains(name)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->identifierToken, Check::tr("duplicate declaration"));
|
2011-09-06 13:59:50 +02:00
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (FunctionDeclaration *decl = cast<FunctionDeclaration *>(ast)) {
|
|
|
|
if (_possiblyUndeclaredUses.contains(name)) {
|
|
|
|
if (_options & Check::WarnUseBeforeDeclaration) {
|
|
|
|
foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
|
|
|
|
warning(loc, Check::tr("function is used before being declared"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_possiblyUndeclaredUses.remove(name);
|
|
|
|
}
|
|
|
|
_declaredFunctions[name] = decl;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void warning(const SourceLocation &loc, const QString &message)
|
|
|
|
{
|
|
|
|
_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
|
|
|
|
}
|
|
|
|
|
|
|
|
Check::Options _options;
|
|
|
|
QList<DiagnosticMessage> _messages;
|
|
|
|
QStringList _formalParameterNames;
|
|
|
|
QHash<QString, VariableDeclaration *> _declaredVariables;
|
|
|
|
QHash<QString, FunctionDeclaration *> _declaredFunctions;
|
|
|
|
QHash<QString, QList<SourceLocation> > _possiblyUndeclaredUses;
|
|
|
|
bool _seenNonDeclarationStatement;
|
|
|
|
};
|
|
|
|
|
2010-02-23 17:02:50 +01:00
|
|
|
} // end of anonymous namespace
|
|
|
|
|
|
|
|
|
2011-07-13 15:04:27 +02:00
|
|
|
Check::Check(Document::Ptr doc, const ContextPtr &context)
|
2010-02-16 10:36:09 +01:00
|
|
|
: _doc(doc)
|
2011-07-13 15:04:27 +02:00
|
|
|
, _context(context)
|
|
|
|
, _scopeChain(doc, _context)
|
2011-07-12 14:55:27 +02:00
|
|
|
, _scopeBuilder(&_scopeChain)
|
2010-11-25 13:38:15 +01:00
|
|
|
, _options(WarnDangerousNonStrictEqualityChecks | WarnBlocks | WarnWith
|
2011-09-06 12:59:30 +02:00
|
|
|
| WarnVoid | WarnCommaExpression | WarnExpressionStatement
|
|
|
|
| WarnAssignInCondition | WarnUseBeforeDeclaration | WarnDuplicateDeclaration
|
|
|
|
| WarnCaseWithoutFlowControlEnd | WarnNonCapitalizedNew
|
2011-09-07 07:21:38 +02:00
|
|
|
| WarnCallsOfCapitalizedFunctions | WarnUnreachablecode
|
|
|
|
| ErrCheckTypeErrors)
|
2010-11-24 15:12:11 +01:00
|
|
|
, _lastValue(0)
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Check::~Check()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<DiagnosticMessage> Check::operator()()
|
|
|
|
{
|
|
|
|
_messages.clear();
|
|
|
|
Node::accept(_doc->ast(), this);
|
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
bool Check::preVisit(Node *ast)
|
|
|
|
{
|
|
|
|
_chain.append(ast);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::postVisit(Node *)
|
|
|
|
{
|
|
|
|
_chain.removeLast();
|
|
|
|
}
|
|
|
|
|
2010-02-19 12:25:26 +01:00
|
|
|
bool Check::visit(UiProgram *)
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
bool Check::visit(UiObjectInitializer *)
|
|
|
|
{
|
2010-12-13 16:46:29 +01:00
|
|
|
m_propertyStack.push(StringSet());
|
|
|
|
UiObjectDefinition *objectDefinition = cast<UiObjectDefinition *>(parent());
|
2011-09-13 09:57:24 +02:00
|
|
|
if (objectDefinition && objectDefinition->qualifiedTypeNameId->name == "Component")
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.push(StringSet());
|
|
|
|
UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
|
2011-09-13 09:57:24 +02:00
|
|
|
if (objectBinding && objectBinding->qualifiedTypeNameId->name == "Component")
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.push(StringSet());
|
|
|
|
if (m_idStack.isEmpty())
|
|
|
|
m_idStack.push(StringSet());
|
|
|
|
return true;
|
2010-12-07 17:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Check::endVisit(UiObjectInitializer *)
|
|
|
|
{
|
|
|
|
m_propertyStack.pop();
|
2010-12-13 16:46:29 +01:00
|
|
|
UiObjectDefinition *objectDenition = cast<UiObjectDefinition *>(parent());
|
2011-09-13 09:57:24 +02:00
|
|
|
if (objectDenition && objectDenition->qualifiedTypeNameId->name == "Component")
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.pop();
|
|
|
|
UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
|
2011-09-13 09:57:24 +02:00
|
|
|
if (objectBinding && objectBinding->qualifiedTypeNameId->name == "Component")
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.pop();
|
2010-12-07 17:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Check::checkProperty(UiQualifiedId *qualifiedId)
|
|
|
|
{
|
|
|
|
const QString id = Bind::toString(qualifiedId);
|
|
|
|
if (id.at(0).isLower()) {
|
|
|
|
if (m_propertyStack.top().contains(id)) {
|
|
|
|
error(fullLocationForQualifiedId(qualifiedId),
|
|
|
|
Check::tr("properties can only be assigned once"));
|
|
|
|
}
|
|
|
|
m_propertyStack.top().insert(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
bool Check::visit(UiObjectDefinition *ast)
|
|
|
|
{
|
2010-02-16 13:28:43 +01:00
|
|
|
visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
|
2010-02-16 10:36:09 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(UiObjectBinding *ast)
|
|
|
|
{
|
|
|
|
checkScopeObjectMember(ast->qualifiedId);
|
2010-12-13 15:08:32 +01:00
|
|
|
if (!ast->hasOnToken)
|
|
|
|
checkProperty(ast->qualifiedId);
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-16 13:28:43 +01:00
|
|
|
visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
|
2010-02-16 11:53:21 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-02-16 13:28:43 +01:00
|
|
|
void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
|
|
|
|
UiObjectInitializer *initializer)
|
2010-02-16 11:53:21 +01:00
|
|
|
{
|
2011-06-23 10:25:43 +02:00
|
|
|
// Don't do type checks if it's a grouped property binding.
|
|
|
|
// For instance: anchors { ... }
|
|
|
|
if (_doc->bind()->isGroupedPropertyBinding(ast)) {
|
2010-02-17 09:29:13 +01:00
|
|
|
checkScopeObjectMember(typeId);
|
|
|
|
// ### don't give up!
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-05-20 13:19:16 +02:00
|
|
|
bool typeError = false;
|
2011-06-24 09:55:14 +02:00
|
|
|
const SourceLocation typeErrorLocation = fullLocationForQualifiedId(typeId);
|
2011-07-13 15:04:27 +02:00
|
|
|
const ObjectValue *prototype = _context->lookupType(_doc.data(), typeId);
|
2011-05-20 13:19:16 +02:00
|
|
|
if (!prototype) {
|
|
|
|
typeError = true;
|
2011-03-01 14:36:09 +01:00
|
|
|
if (_options & ErrCheckTypeErrors)
|
2011-06-24 09:55:14 +02:00
|
|
|
error(typeErrorLocation,
|
2010-05-19 13:32:11 +02:00
|
|
|
Check::tr("unknown type"));
|
2011-05-20 13:19:16 +02:00
|
|
|
} else {
|
2011-07-13 15:04:27 +02:00
|
|
|
PrototypeIterator iter(prototype, _context);
|
2011-05-20 13:19:16 +02:00
|
|
|
QList<const ObjectValue *> prototypes = iter.all();
|
|
|
|
if (iter.error() != PrototypeIterator::NoError)
|
|
|
|
typeError = true;
|
|
|
|
if (_options & ErrCheckTypeErrors) {
|
|
|
|
const ObjectValue *lastPrototype = prototypes.last();
|
|
|
|
if (iter.error() == PrototypeIterator::ReferenceResolutionError) {
|
|
|
|
if (const QmlPrototypeReference *ref =
|
|
|
|
dynamic_cast<const QmlPrototypeReference *>(lastPrototype->prototype())) {
|
2011-06-24 09:55:14 +02:00
|
|
|
error(typeErrorLocation,
|
2011-05-20 13:19:16 +02:00
|
|
|
Check::tr("could not resolve the prototype %1 of %2").arg(
|
|
|
|
Bind::toString(ref->qmlTypeName()), lastPrototype->className()));
|
|
|
|
} else {
|
2011-06-24 09:55:14 +02:00
|
|
|
error(typeErrorLocation,
|
2011-05-20 13:19:16 +02:00
|
|
|
Check::tr("could not resolve the prototype of %1").arg(
|
|
|
|
lastPrototype->className()));
|
|
|
|
}
|
|
|
|
} else if (iter.error() == PrototypeIterator::CycleError) {
|
2011-06-24 09:55:14 +02:00
|
|
|
error(typeErrorLocation,
|
2011-05-20 13:19:16 +02:00
|
|
|
Check::tr("prototype cycle, the last non-repeated object is %1").arg(
|
|
|
|
lastPrototype->className()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_scopeBuilder.push(ast);
|
|
|
|
|
|
|
|
if (typeError) {
|
2010-02-19 15:55:11 +01:00
|
|
|
// suppress subsequent errors about scope object lookup by clearing
|
|
|
|
// the scope object list
|
|
|
|
// ### todo: better way?
|
2011-07-12 14:55:27 +02:00
|
|
|
_scopeChain.setQmlScopeObjects(QList<const ObjectValue *>());
|
2010-02-16 13:28:43 +01:00
|
|
|
}
|
|
|
|
|
2010-02-16 11:53:21 +01:00
|
|
|
Node::accept(initializer, this);
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-19 15:55:11 +01:00
|
|
|
_scopeBuilder.pop();
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(UiScriptBinding *ast)
|
|
|
|
{
|
2010-02-23 14:55:38 +01:00
|
|
|
// special case for id property
|
2011-09-13 09:57:24 +02:00
|
|
|
if (ast->qualifiedId->name == QLatin1String("id") && ! ast->qualifiedId->next) {
|
2010-02-23 14:55:38 +01:00
|
|
|
if (! ast->statement)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
|
|
|
|
ast->statement->lastSourceLocation());
|
|
|
|
|
|
|
|
ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
|
|
|
|
if (!expStmt) {
|
2010-05-19 13:32:11 +02:00
|
|
|
error(loc, Check::tr("expected id"));
|
2010-02-23 14:55:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-03-04 16:40:47 +01:00
|
|
|
QString id;
|
|
|
|
if (IdentifierExpression *idExp = cast<IdentifierExpression *>(expStmt->expression)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
id = idExp->name.toString();
|
2010-03-04 16:40:47 +01:00
|
|
|
} else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
id = strExp->value.toString();
|
2010-05-19 13:32:11 +02:00
|
|
|
warning(loc, Check::tr("using string literals for ids is discouraged"));
|
2010-03-04 16:40:47 +01:00
|
|
|
} else {
|
2010-05-19 13:32:11 +02:00
|
|
|
error(loc, Check::tr("expected id"));
|
2010-02-23 14:55:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
if (id.isEmpty() || (!id.at(0).isLower() && id.at(0) != '_')) {
|
2010-12-07 17:31:53 +01:00
|
|
|
error(loc, Check::tr("ids must be lower case or start with underscore"));
|
2010-02-23 14:55:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
2010-12-07 17:31:53 +01:00
|
|
|
|
2010-12-13 16:46:29 +01:00
|
|
|
if (m_idStack.top().contains(id)) {
|
2010-12-07 17:31:53 +01:00
|
|
|
error(loc, Check::tr("ids must be unique"));
|
|
|
|
return false;
|
|
|
|
}
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.top().insert(id);
|
2010-02-23 14:55:38 +01:00
|
|
|
}
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
checkProperty(ast->qualifiedId);
|
|
|
|
|
2011-09-01 15:04:21 +02:00
|
|
|
if (!ast->statement)
|
|
|
|
return false;
|
|
|
|
|
2010-02-19 15:10:39 +01:00
|
|
|
const Value *lhsValue = checkScopeObjectMember(ast->qualifiedId);
|
|
|
|
if (lhsValue) {
|
2011-09-01 15:04:21 +02:00
|
|
|
Evaluate evaluator(&_scopeChain);
|
|
|
|
const Value *rhsValue = evaluator(ast->statement);
|
2010-02-19 15:10:39 +01:00
|
|
|
|
2011-09-01 15:04:21 +02:00
|
|
|
const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
|
|
|
|
ast->statement->lastSourceLocation());
|
|
|
|
AssignmentCheck assignmentCheck;
|
|
|
|
DiagnosticMessage message = assignmentCheck(_doc, loc, lhsValue, rhsValue, ast->statement);
|
|
|
|
if (! message.message.isEmpty())
|
|
|
|
_messages += message;
|
2010-02-19 15:10:39 +01:00
|
|
|
}
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
checkBindingRhs(ast->statement);
|
2010-12-06 10:03:53 +01:00
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
Node::accept(ast->qualifiedId, this);
|
|
|
|
_scopeBuilder.push(ast);
|
|
|
|
Node::accept(ast->statement, this);
|
|
|
|
_scopeBuilder.pop();
|
|
|
|
|
|
|
|
return false;
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(UiArrayBinding *ast)
|
|
|
|
{
|
|
|
|
checkScopeObjectMember(ast->qualifiedId);
|
2010-12-07 17:31:53 +01:00
|
|
|
checkProperty(ast->qualifiedId);
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-05 15:37:14 +02:00
|
|
|
bool Check::visit(UiPublicMember *ast)
|
|
|
|
{
|
|
|
|
// check if the member type is valid
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!ast->memberType.isEmpty()) {
|
|
|
|
const QString &name = ast->memberType.toString();
|
2011-09-05 15:37:14 +02:00
|
|
|
if (!name.isEmpty() && name.at(0).isLower()) {
|
|
|
|
if (!isValidBuiltinPropertyType(name))
|
|
|
|
error(ast->typeToken, tr("'%1' is not a valid property type").arg(name));
|
|
|
|
}
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
|
|
|
|
checkBindingRhs(ast->statement);
|
|
|
|
|
|
|
|
_scopeBuilder.push(ast);
|
|
|
|
Node::accept(ast->statement, this);
|
|
|
|
Node::accept(ast->binding, this);
|
|
|
|
_scopeBuilder.pop();
|
|
|
|
|
|
|
|
return false;
|
2011-09-05 15:37:14 +02:00
|
|
|
}
|
|
|
|
|
2010-11-24 15:12:11 +01:00
|
|
|
bool Check::visit(IdentifierExpression *ast)
|
|
|
|
{
|
|
|
|
// currently disabled: too many false negatives
|
|
|
|
return true;
|
|
|
|
|
|
|
|
_lastValue = 0;
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!ast->name.isEmpty()) {
|
2011-07-12 14:55:27 +02:00
|
|
|
Evaluate evaluator(&_scopeChain);
|
2010-11-24 15:12:11 +01:00
|
|
|
_lastValue = evaluator.reference(ast);
|
|
|
|
if (!_lastValue)
|
|
|
|
error(ast->identifierToken, tr("unknown identifier"));
|
|
|
|
if (const Reference *ref = value_cast<const Reference *>(_lastValue)) {
|
2011-07-13 15:04:27 +02:00
|
|
|
_lastValue = _context->lookupReference(ref);
|
2010-11-24 15:12:11 +01:00
|
|
|
if (!_lastValue)
|
|
|
|
error(ast->identifierToken, tr("could not resolve"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(FieldMemberExpression *ast)
|
|
|
|
{
|
|
|
|
// currently disabled: too many false negatives
|
|
|
|
return true;
|
|
|
|
|
|
|
|
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"));
|
|
|
|
}
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!obj || ast->name.isEmpty()) {
|
2010-11-24 15:12:11 +01:00
|
|
|
_lastValue = 0;
|
|
|
|
return false;
|
|
|
|
}
|
2011-09-13 09:57:24 +02:00
|
|
|
_lastValue = obj->lookupMember(ast->name.toString(), _context);
|
2010-11-24 15:12:11 +01:00
|
|
|
if (!_lastValue)
|
|
|
|
error(ast->identifierToken, tr("unknown member"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(FunctionDeclaration *ast)
|
|
|
|
{
|
|
|
|
return visit(static_cast<FunctionExpression *>(ast));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(FunctionExpression *ast)
|
|
|
|
{
|
2011-09-06 13:59:50 +02:00
|
|
|
DeclarationsCheck bodyCheck;
|
2011-09-07 07:21:38 +02:00
|
|
|
_messages += bodyCheck(ast, _options);
|
|
|
|
if (_options & WarnUnreachablecode) {
|
|
|
|
MarkUnreachableCode unreachableCheck;
|
|
|
|
_messages += unreachableCheck(ast->body);
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
|
2010-11-24 15:12:11 +01:00
|
|
|
Node::accept(ast->formals, this);
|
|
|
|
_scopeBuilder.push(ast);
|
|
|
|
Node::accept(ast->body, this);
|
|
|
|
_scopeBuilder.pop();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-12 10:44:11 +02:00
|
|
|
static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs)
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-09-12 10:44:11 +02:00
|
|
|
// we currently use undefined as a "we don't know" value
|
|
|
|
if (lhs->asUndefinedValue() || rhs->asUndefinedValue())
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
2011-09-12 10:44:11 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
if (lhs->asBooleanValue() && !rhs->asBooleanValue())
|
|
|
|
return true; // coerces bool to number
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(BinaryExpression *ast)
|
|
|
|
{
|
|
|
|
if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) {
|
2010-11-29 11:20:07 +01:00
|
|
|
bool warn = _options & WarnAllNonStrictEqualityChecks;
|
|
|
|
if (!warn && _options & WarnDangerousNonStrictEqualityChecks) {
|
2011-07-12 14:55:27 +02:00
|
|
|
Evaluate eval(&_scopeChain);
|
2010-11-29 11:20:07 +01:00
|
|
|
const Value *lhs = eval(ast->left);
|
|
|
|
const Value *rhs = eval(ast->right);
|
2011-09-12 10:44:11 +02:00
|
|
|
warn = shouldAvoidNonStrictEqualityCheck(lhs, rhs)
|
|
|
|
|| shouldAvoidNonStrictEqualityCheck(rhs, lhs);
|
2010-11-29 11:20:07 +01:00
|
|
|
}
|
|
|
|
if (warn) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->operatorToken, tr("== and != perform type coercion, use === or !== instead to avoid"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(Block *ast)
|
|
|
|
{
|
|
|
|
if (Node *p = parent()) {
|
|
|
|
if (_options & WarnBlocks
|
|
|
|
&& !cast<UiScriptBinding *>(p)
|
2011-09-07 11:01:13 +02:00
|
|
|
&& !cast<UiPublicMember *>(p)
|
2010-11-25 13:38:15 +01:00
|
|
|
&& !cast<TryStatement *>(p)
|
|
|
|
&& !cast<Catch *>(p)
|
|
|
|
&& !cast<Finally *>(p)
|
|
|
|
&& !cast<ForStatement *>(p)
|
|
|
|
&& !cast<ForEachStatement *>(p)
|
2010-12-06 09:54:10 +01:00
|
|
|
&& !cast<LocalForStatement *>(p)
|
|
|
|
&& !cast<LocalForEachStatement *>(p)
|
2010-11-25 13:38:15 +01:00
|
|
|
&& !cast<DoWhileStatement *>(p)
|
|
|
|
&& !cast<WhileStatement *>(p)
|
|
|
|
&& !cast<IfStatement *>(p)
|
|
|
|
&& !cast<SwitchStatement *>(p)
|
|
|
|
&& !cast<WithStatement *>(p)) {
|
|
|
|
warning(ast->lbraceToken, tr("blocks do not introduce a new scope, avoid"));
|
|
|
|
}
|
2011-09-07 11:01:13 +02:00
|
|
|
if (!ast->statements
|
|
|
|
&& (cast<UiPublicMember *>(p)
|
|
|
|
|| cast<UiScriptBinding *>(p))) {
|
|
|
|
warning(locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()),
|
|
|
|
tr("unintentional empty block, use ({}) for empty object literal"));
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(WithStatement *ast)
|
|
|
|
{
|
|
|
|
if (_options & WarnWith)
|
|
|
|
warning(ast->withToken, tr("use of the with statement is not recommended, use a var instead"));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(VoidExpression *ast)
|
|
|
|
{
|
|
|
|
if (_options & WarnVoid)
|
|
|
|
warning(ast->voidToken, tr("use of void is usually confusing and not recommended"));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(Expression *ast)
|
|
|
|
{
|
|
|
|
if (_options & WarnCommaExpression && ast->left && ast->right) {
|
2010-12-06 09:54:10 +01:00
|
|
|
Node *p = parent();
|
|
|
|
if (!cast<ForStatement *>(p)
|
|
|
|
&& !cast<LocalForStatement *>(p)) {
|
2010-11-25 13:38:15 +01:00
|
|
|
warning(ast->commaToken, tr("avoid comma expressions"));
|
2010-12-06 09:54:10 +01:00
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(ExpressionStatement *ast)
|
|
|
|
{
|
|
|
|
if (_options & WarnExpressionStatement && ast->expression) {
|
|
|
|
bool ok = cast<CallExpression *>(ast->expression)
|
|
|
|
|| cast<DeleteExpression *>(ast->expression)
|
|
|
|
|| cast<PreDecrementExpression *>(ast->expression)
|
|
|
|
|| cast<PreIncrementExpression *>(ast->expression)
|
|
|
|
|| cast<PostIncrementExpression *>(ast->expression)
|
2011-04-21 09:43:24 +02:00
|
|
|
|| cast<PostDecrementExpression *>(ast->expression)
|
|
|
|
|| cast<FunctionExpression *>(ast->expression);
|
2010-11-25 13:38:15 +01:00
|
|
|
if (BinaryExpression *binary = cast<BinaryExpression *>(ast->expression)) {
|
|
|
|
switch (binary->op) {
|
|
|
|
case QSOperator::Assign:
|
|
|
|
case QSOperator::InplaceAdd:
|
|
|
|
case QSOperator::InplaceAnd:
|
|
|
|
case QSOperator::InplaceDiv:
|
|
|
|
case QSOperator::InplaceLeftShift:
|
|
|
|
case QSOperator::InplaceRightShift:
|
|
|
|
case QSOperator::InplaceMod:
|
|
|
|
case QSOperator::InplaceMul:
|
|
|
|
case QSOperator::InplaceOr:
|
|
|
|
case QSOperator::InplaceSub:
|
|
|
|
case QSOperator::InplaceURightShift:
|
|
|
|
case QSOperator::InplaceXor:
|
|
|
|
ok = true;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
2010-11-29 12:21:02 +01:00
|
|
|
if (!ok) {
|
|
|
|
for (int i = 0; Node *p = parent(i); ++i) {
|
|
|
|
if (UiScriptBinding *binding = cast<UiScriptBinding *>(p)) {
|
|
|
|
if (!cast<Block *>(binding->statement)) {
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-05-12 13:25:35 +02:00
|
|
|
if (UiPublicMember *member = cast<UiPublicMember *>(p)) {
|
|
|
|
if (!cast<Block *>(member->statement)) {
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-11-29 12:21:02 +01:00
|
|
|
}
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
warning(locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()),
|
|
|
|
tr("expression statements should be assignments, calls or delete expressions only"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(IfStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->expression)
|
|
|
|
checkAssignInCondition(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(ForStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->condition)
|
|
|
|
checkAssignInCondition(ast->condition);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-12-06 09:54:10 +01:00
|
|
|
bool Check::visit(LocalForStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->condition)
|
|
|
|
checkAssignInCondition(ast->condition);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
bool Check::visit(WhileStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->expression)
|
|
|
|
checkAssignInCondition(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(DoWhileStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->expression)
|
|
|
|
checkAssignInCondition(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(CaseClause *ast)
|
|
|
|
{
|
|
|
|
checkEndsWithControlFlow(ast->statements, ast->caseToken);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(DefaultClause *ast)
|
|
|
|
{
|
|
|
|
checkEndsWithControlFlow(ast->statements, ast->defaultToken);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-06 12:59:30 +02:00
|
|
|
static QString functionName(ExpressionNode *ast, SourceLocation *location)
|
|
|
|
{
|
|
|
|
if (IdentifierExpression *id = cast<IdentifierExpression *>(ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!id->name.isEmpty()) {
|
2011-09-06 12:59:30 +02:00
|
|
|
*location = id->identifierToken;
|
2011-09-13 09:57:24 +02:00
|
|
|
return id->name.toString();
|
2011-09-06 12:59:30 +02:00
|
|
|
}
|
|
|
|
} else if (FieldMemberExpression *fme = cast<FieldMemberExpression *>(ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!fme->name.isEmpty()) {
|
2011-09-06 12:59:30 +02:00
|
|
|
*location = fme->identifierToken;
|
2011-09-13 09:57:24 +02:00
|
|
|
return fme->name.toString();
|
2011-09-06 12:59:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::checkNewExpression(ExpressionNode *ast)
|
|
|
|
{
|
|
|
|
if (!(_options & WarnNonCapitalizedNew))
|
|
|
|
return;
|
|
|
|
SourceLocation location;
|
|
|
|
const QString name = functionName(ast, &location);
|
|
|
|
if (name.isEmpty())
|
|
|
|
return;
|
|
|
|
if (!name.at(0).isUpper()) {
|
|
|
|
warning(location, tr("'new' should only be used with functions that start with an uppercase letter"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
void Check::checkBindingRhs(Statement *statement)
|
|
|
|
{
|
|
|
|
if (!statement)
|
|
|
|
return;
|
|
|
|
|
|
|
|
DeclarationsCheck bodyCheck;
|
|
|
|
_messages += bodyCheck(statement, _options);
|
|
|
|
if (_options & WarnUnreachablecode) {
|
|
|
|
MarkUnreachableCode unreachableCheck;
|
|
|
|
_messages += unreachableCheck(statement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-06 12:59:30 +02:00
|
|
|
bool Check::visit(NewExpression *ast)
|
|
|
|
{
|
|
|
|
checkNewExpression(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(NewMemberExpression *ast)
|
|
|
|
{
|
|
|
|
checkNewExpression(ast->base);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(CallExpression *ast)
|
|
|
|
{
|
|
|
|
// check for capitalized function name being called
|
|
|
|
if (_options & WarnCallsOfCapitalizedFunctions) {
|
|
|
|
SourceLocation location;
|
|
|
|
const QString name = functionName(ast->base, &location);
|
|
|
|
if (!name.isEmpty() && name.at(0).isUpper()) {
|
|
|
|
warning(location, tr("calls of functions that start with an uppercase letter should use 'new'"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-04-06 11:44:55 +02:00
|
|
|
/// When something is changed here, also change ReadingContext::lookupProperty in
|
|
|
|
/// texttomodelmerger.cpp
|
|
|
|
/// ### Maybe put this into the context as a helper method.
|
2010-02-19 15:10:39 +01:00
|
|
|
const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
2011-07-12 14:55:27 +02:00
|
|
|
QList<const ObjectValue *> scopeObjects = _scopeChain.qmlScopeObjects();
|
2010-02-19 15:55:11 +01:00
|
|
|
if (scopeObjects.isEmpty())
|
|
|
|
return 0;
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
if (! id)
|
2010-02-19 15:10:39 +01:00
|
|
|
return 0; // ### error?
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
if (id->name.isEmpty()) // possible after error recovery
|
2010-04-06 11:44:55 +02:00
|
|
|
return 0;
|
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
QString propertyName = id->name.toString();
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
if (propertyName == QLatin1String("id") && ! id->next)
|
2010-02-19 15:10:39 +01:00
|
|
|
return 0; // ### should probably be a special value
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
// attached properties
|
2010-02-18 15:01:26 +01:00
|
|
|
bool isAttachedProperty = false;
|
|
|
|
if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
|
|
|
|
isAttachedProperty = true;
|
2011-07-12 14:55:27 +02:00
|
|
|
if (const ObjectValue *qmlTypes = _scopeChain.qmlTypes())
|
2010-08-30 13:31:50 +02:00
|
|
|
scopeObjects += qmlTypes;
|
2010-02-18 15:01:26 +01:00
|
|
|
}
|
2010-02-18 10:42:15 +01:00
|
|
|
|
2010-02-19 10:14:34 +01:00
|
|
|
if (scopeObjects.isEmpty())
|
2010-02-19 15:10:39 +01:00
|
|
|
return 0;
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-18 14:21:53 +01:00
|
|
|
// global lookup for first part of id
|
2010-02-19 10:14:34 +01:00
|
|
|
const Value *value = 0;
|
|
|
|
for (int i = scopeObjects.size() - 1; i >= 0; --i) {
|
2011-07-13 15:04:27 +02:00
|
|
|
value = scopeObjects[i]->lookupMember(propertyName, _context);
|
2010-02-19 10:14:34 +01:00
|
|
|
if (value)
|
|
|
|
break;
|
|
|
|
}
|
2010-02-16 10:36:09 +01:00
|
|
|
if (!value) {
|
|
|
|
error(id->identifierToken,
|
2010-05-19 13:32:11 +02:00
|
|
|
Check::tr("'%1' is not a valid property name").arg(propertyName));
|
2011-05-04 11:12:45 +02:00
|
|
|
return 0;
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
2010-02-18 15:01:26 +01:00
|
|
|
// can't look up members for attached properties
|
|
|
|
if (isAttachedProperty)
|
2010-02-19 15:10:39 +01:00
|
|
|
return 0;
|
2010-02-18 15:01:26 +01:00
|
|
|
|
2011-05-04 11:12:45 +02:00
|
|
|
// resolve references
|
|
|
|
if (const Reference *ref = value->asReference())
|
2011-07-13 15:04:27 +02:00
|
|
|
value = _context->lookupReference(ref);
|
2011-05-04 11:12:45 +02:00
|
|
|
|
2010-02-18 14:21:53 +01:00
|
|
|
// member lookup
|
|
|
|
const UiQualifiedId *idPart = id;
|
|
|
|
while (idPart->next) {
|
|
|
|
const ObjectValue *objectValue = value_cast<const ObjectValue *>(value);
|
|
|
|
if (! objectValue) {
|
|
|
|
error(idPart->identifierToken,
|
2010-05-19 13:32:11 +02:00
|
|
|
Check::tr("'%1' does not have members").arg(propertyName));
|
2010-02-19 15:10:39 +01:00
|
|
|
return 0;
|
2010-02-18 14:21:53 +01:00
|
|
|
}
|
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
if (idPart->next->name.isEmpty()) {
|
2010-02-24 17:14:14 +01:00
|
|
|
// somebody typed "id." and error recovery still gave us a valid tree,
|
|
|
|
// so just bail out here.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-18 14:21:53 +01:00
|
|
|
idPart = idPart->next;
|
2011-09-13 09:57:24 +02:00
|
|
|
propertyName = idPart->name.toString();
|
2010-02-18 14:21:53 +01:00
|
|
|
|
2011-07-13 15:04:27 +02:00
|
|
|
value = objectValue->lookupMember(propertyName, _context);
|
2010-02-18 14:21:53 +01:00
|
|
|
if (! value) {
|
|
|
|
error(idPart->identifierToken,
|
2010-05-19 13:32:11 +02:00
|
|
|
Check::tr("'%1' is not a member of '%2'").arg(
|
2010-04-28 10:32:45 +02:00
|
|
|
propertyName, objectValue->className()));
|
2010-02-19 15:10:39 +01:00
|
|
|
return 0;
|
2010-02-18 14:21:53 +01:00
|
|
|
}
|
|
|
|
}
|
2010-02-19 15:10:39 +01:00
|
|
|
|
|
|
|
return value;
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
void Check::checkAssignInCondition(AST::ExpressionNode *condition)
|
|
|
|
{
|
|
|
|
if (_options & WarnAssignInCondition) {
|
|
|
|
if (BinaryExpression *binary = cast<BinaryExpression *>(condition)) {
|
|
|
|
if (binary->op == QSOperator::Assign)
|
|
|
|
warning(binary->operatorToken, tr("avoid assignments in conditions"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::checkEndsWithControlFlow(StatementList *statements, SourceLocation errorLoc)
|
|
|
|
{
|
|
|
|
if (!statements || !(_options & WarnCaseWithoutFlowControlEnd))
|
|
|
|
return;
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
ReachesEndCheck check;
|
|
|
|
if (check(statements)) {
|
|
|
|
warning(errorLoc, tr("case is not terminated and not empty"));
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
void Check::error(const AST::SourceLocation &loc, const QString &message)
|
|
|
|
{
|
|
|
|
_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::warning(const AST::SourceLocation &loc, const QString &message)
|
|
|
|
{
|
|
|
|
_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
Node *Check::parent(int distance)
|
|
|
|
{
|
|
|
|
const int index = _chain.size() - 2 - distance;
|
|
|
|
if (index < 0)
|
|
|
|
return 0;
|
|
|
|
return _chain.at(index);
|
|
|
|
}
|