Files
qt-creator/src/libs/qmljs/qmljscheck.cpp

976 lines
32 KiB
C++
Raw Normal View History

/**************************************************************************
**
** 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).
**
2011-04-13 08:42:33 +02:00
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** 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-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
** Nokia at info@qt.nokia.com.
**
**************************************************************************/
#include "qmljscheck.h"
#include "qmljsbind.h"
#include "qmljscontext.h"
#include "qmljsevaluate.h"
#include "parser/qmljsast_p.h"
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtGui/QColor>
#include <QtGui/QApplication>
using namespace QmlJS;
using namespace QmlJS::AST;
using namespace QmlJS::Interpreter;
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;
}
SourceLocation QmlJS::locationFromRange(const SourceLocation &start,
const SourceLocation &end)
{
return SourceLocation(start.offset,
end.end() - start.begin(),
start.startLine,
start.startColumn);
}
SourceLocation QmlJS::fullLocationForQualifiedId(AST::UiQualifiedId *qualifiedId)
{
SourceLocation start = qualifiedId->identifierToken;
SourceLocation end = qualifiedId->identifierToken;
for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
if (iter->name)
end = iter->identifierToken;
}
return locationFromRange(start, end);
}
DiagnosticMessage QmlJS::errorMessage(const AST::SourceLocation &loc, const QString &message)
{
return DiagnosticMessage(DiagnosticMessage::Error, loc, message);
}
namespace {
class AssignmentCheck : public ValueVisitor
{
public:
DiagnosticMessage operator()(
const Document::Ptr &document,
const SourceLocation &location,
const Interpreter::Value *lhsValue,
const Interpreter::Value *rhsValue,
ExpressionNode *ast)
{
_doc = document;
_message = DiagnosticMessage(DiagnosticMessage::Error, location, QString());
_rhsValue = rhsValue;
_ast = ast;
if (lhsValue)
lhsValue->accept(this);
return _message;
}
virtual void visit(const NumberValue *value)
{
if (const QmlEnumValue *enumValue = dynamic_cast<const QmlEnumValue *>(value)) {
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
const QString valueName = stringLiteral->value->asString();
if (!enumValue->keys().contains(valueName)) {
_message.message = Check::tr("unknown value for enum");
}
} else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
&& ! _rhsValue->asUndefinedValue()) {
_message.message = Check::tr("enum value is not a string or number");
}
} else {
if (/*cast<StringLiteral *>(_ast)
||*/ _ast->kind == Node::Kind_TrueLiteral
|| _ast->kind == Node::Kind_FalseLiteral) {
_message.message = Check::tr("numerical value expected");
}
}
}
virtual void visit(const BooleanValue *)
{
UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
if (cast<StringLiteral *>(_ast)
|| cast<NumericLiteral *>(_ast)
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) {
_message.message = Check::tr("boolean value expected");
}
}
virtual void visit(const StringValue *value)
{
UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
if (cast<NumericLiteral *>(_ast)
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))
|| _ast->kind == Node::Kind_TrueLiteral
|| _ast->kind == Node::Kind_FalseLiteral) {
_message.message = Check::tr("string value expected");
}
if (value && value->asUrlValue()) {
if (StringLiteral *literal = cast<StringLiteral *>(_ast)) {
QUrl url(literal->value->asString());
if (!url.isValid() && !url.isEmpty()) {
_message.message = Check::tr("not a valid url");
} else {
QString fileName = url.toLocalFile();
if (!fileName.isEmpty()) {
if (QFileInfo(fileName).isRelative()) {
fileName.prepend(QDir::separator());
fileName.prepend(_doc->path());
}
if (!QFileInfo(fileName).exists()) {
_message.message = Check::tr("file or directory does not exist");
_message.kind = DiagnosticMessage::Warning;
}
}
}
}
}
}
virtual void visit(const ColorValue *)
{
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
if (!toQColor(stringLiteral->value->asString()).isValid())
_message.message = Check::tr("not a valid color");
} else {
visit((StringValue *)0);
}
}
virtual void visit(const AnchorLineValue *)
{
if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUndefinedValue()))
_message.message = Check::tr("expected anchor line");
}
Document::Ptr _doc;
DiagnosticMessage _message;
const Value *_rhsValue;
ExpressionNode *_ast;
};
class FunctionBodyCheck : protected Visitor
{
public:
QList<DiagnosticMessage> operator()(FunctionExpression *function, Check::Options options)
{
clear();
_options = options;
for (FormalParameterList *plist = function->formals; plist; plist = plist->next) {
if (plist->name)
_formalParameterNames += plist->name->asString();
}
Node::accept(function->body, this);
return _messages;
}
QList<DiagnosticMessage> operator()(StatementList *statements, Check::Options options)
{
clear();
_options = options;
Node::accept(statements, this);
return _messages;
}
protected:
void clear()
{
_messages.clear();
_declaredFunctions.clear();
_declaredVariables.clear();
_possiblyUndeclaredUses.clear();
_seenNonDeclarationStatement = false;
_formalParameterNames.clear();
}
void postVisit(Node *ast)
{
if (!_seenNonDeclarationStatement && ast->statementCast()
&& !cast<VariableStatement *>(ast)) {
_seenNonDeclarationStatement = true;
}
}
bool visit(IdentifierExpression *ast)
{
if (!ast->name)
return false;
const QString name = ast->name->asString();
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)
{
if (!ast->name)
return true;
const QString name = ast->name->asString();
if (_formalParameterNames.contains(name)) {
if (_options & Check::WarnDuplicateDeclaration)
warning(ast->identifierToken, Check::tr("already a formal parameter"));
return true;
}
if (_declaredFunctions.contains(name)) {
if (_options & Check::WarnDuplicateDeclaration)
warning(ast->identifierToken, Check::tr("already declared as function"));
return true;
}
if (_declaredVariables.contains(name)) {
if (_options & Check::WarnDuplicateDeclaration)
warning(ast->identifierToken, Check::tr("duplicate declaration"));
return true;
}
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)
{
if (!ast->name)
return false;
const QString name = ast->name->asString();
if (_formalParameterNames.contains(name)) {
if (_options & Check::WarnDuplicateDeclaration)
warning(ast->identifierToken, Check::tr("already a formal parameter"));
return false;
}
if (_declaredVariables.contains(name)) {
if (_options & Check::WarnDuplicateDeclaration)
warning(ast->identifierToken, Check::tr("already declared as var"));
return false;
}
if (_declaredFunctions.contains(name)) {
if (_options & Check::WarnDuplicateDeclaration)
warning(ast->identifierToken, Check::tr("duplicate declaration"));
return false;
}
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;
};
} // end of anonymous namespace
Check::Check(Document::Ptr doc, const Context *linkedContextNoScope)
: _doc(doc)
, _context(*linkedContextNoScope)
, _scopeBuilder(&_context, doc)
, _options(WarnDangerousNonStrictEqualityChecks | WarnBlocks | WarnWith
| WarnVoid | WarnCommaExpression | WarnExpressionStatement
| WarnAssignInCondition | WarnUseBeforeDeclaration | WarnDuplicateDeclaration
| WarnCaseWithoutFlowControlEnd | ErrCheckTypeErrors)
, _lastValue(0)
{
_scopeBuilder.initializeRootScope();
}
Check::~Check()
{
}
QList<DiagnosticMessage> Check::operator()()
{
_messages.clear();
Node::accept(_doc->ast(), this);
return _messages;
}
bool Check::preVisit(Node *ast)
{
_chain.append(ast);
return true;
}
void Check::postVisit(Node *)
{
_chain.removeLast();
}
bool Check::visit(UiProgram *)
{
return true;
}
bool Check::visit(UiObjectInitializer *)
{
m_propertyStack.push(StringSet());
UiObjectDefinition *objectDefinition = cast<UiObjectDefinition *>(parent());
if (objectDefinition && objectDefinition->qualifiedTypeNameId->name->asString() == "Component")
m_idStack.push(StringSet());
UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
if (objectBinding && objectBinding->qualifiedTypeNameId->name->asString() == "Component")
m_idStack.push(StringSet());
if (m_idStack.isEmpty())
m_idStack.push(StringSet());
return true;
}
void Check::endVisit(UiObjectInitializer *)
{
m_propertyStack.pop();
UiObjectDefinition *objectDenition = cast<UiObjectDefinition *>(parent());
if (objectDenition && objectDenition->qualifiedTypeNameId->name->asString() == "Component")
m_idStack.pop();
UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
if (objectBinding && objectBinding->qualifiedTypeNameId->name->asString() == "Component")
m_idStack.pop();
}
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);
}
}
bool Check::visit(UiObjectDefinition *ast)
{
visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
return false;
}
bool Check::visit(UiObjectBinding *ast)
{
checkScopeObjectMember(ast->qualifiedId);
if (!ast->hasOnToken)
checkProperty(ast->qualifiedId);
visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
return false;
}
void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
UiObjectInitializer *initializer)
{
// Don't do type checks if it's a grouped property binding.
// For instance: anchors { ... }
if (_doc->bind()->isGroupedPropertyBinding(ast)) {
checkScopeObjectMember(typeId);
// ### don't give up!
return;
}
bool typeError = false;
const SourceLocation typeErrorLocation = fullLocationForQualifiedId(typeId);
const ObjectValue *prototype = _context.lookupType(_doc.data(), typeId);
if (!prototype) {
typeError = true;
if (_options & ErrCheckTypeErrors)
error(typeErrorLocation,
Check::tr("unknown type"));
} else {
PrototypeIterator iter(prototype, &_context);
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())) {
error(typeErrorLocation,
Check::tr("could not resolve the prototype %1 of %2").arg(
Bind::toString(ref->qmlTypeName()), lastPrototype->className()));
} else {
error(typeErrorLocation,
Check::tr("could not resolve the prototype of %1").arg(
lastPrototype->className()));
}
} else if (iter.error() == PrototypeIterator::CycleError) {
error(typeErrorLocation,
Check::tr("prototype cycle, the last non-repeated object is %1").arg(
lastPrototype->className()));
}
}
}
_scopeBuilder.push(ast);
if (typeError) {
// suppress subsequent errors about scope object lookup by clearing
// the scope object list
// ### todo: better way?
_context.scopeChain().qmlScopeObjects.clear();
_context.scopeChain().update();
}
Node::accept(initializer, this);
_scopeBuilder.pop();
}
bool Check::visit(UiScriptBinding *ast)
{
// special case for id property
if (ast->qualifiedId->name->asString() == QLatin1String("id") && ! ast->qualifiedId->next) {
if (! ast->statement)
return false;
const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
ast->statement->lastSourceLocation());
ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
error(loc, Check::tr("expected id"));
return false;
}
QString id;
if (IdentifierExpression *idExp = cast<IdentifierExpression *>(expStmt->expression)) {
id = idExp->name->asString();
} else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) {
id = strExp->value->asString();
warning(loc, Check::tr("using string literals for ids is discouraged"));
} else {
error(loc, Check::tr("expected id"));
return false;
}
if (id.isEmpty() || (!id[0].isLower() && id[0] != '_')) {
error(loc, Check::tr("ids must be lower case or start with underscore"));
return false;
}
if (m_idStack.top().contains(id)) {
error(loc, Check::tr("ids must be unique"));
return false;
}
m_idStack.top().insert(id);
}
checkProperty(ast->qualifiedId);
const Value *lhsValue = checkScopeObjectMember(ast->qualifiedId);
if (lhsValue) {
// ### Fix the evaluator to accept statements!
if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement)) {
ExpressionNode *expr = expStmt->expression;
Evaluate evaluator(&_context);
const Value *rhsValue = evaluator(expr);
const SourceLocation loc = locationFromRange(expStmt->firstSourceLocation(),
expStmt->lastSourceLocation());
AssignmentCheck assignmentCheck;
DiagnosticMessage message = assignmentCheck(_doc, loc, lhsValue, rhsValue, expr);
if (! message.message.isEmpty())
_messages += message;
}
}
if (Block *block = cast<Block *>(ast->statement)) {
FunctionBodyCheck bodyCheck;
_messages.append(bodyCheck(block->statements, _options));
Node::accept(ast->qualifiedId, this);
_scopeBuilder.push(ast);
Node::accept(block->statements, this);
_scopeBuilder.pop();
return false;
}
return true;
}
bool Check::visit(UiArrayBinding *ast)
{
checkScopeObjectMember(ast->qualifiedId);
checkProperty(ast->qualifiedId);
return true;
}
bool Check::visit(IdentifierExpression *ast)
{
// currently disabled: too many false negatives
return true;
_lastValue = 0;
if (ast->name) {
Evaluate evaluator(&_context);
_lastValue = evaluator.reference(ast);
if (!_lastValue)
error(ast->identifierToken, tr("unknown identifier"));
if (const Reference *ref = value_cast<const Reference *>(_lastValue)) {
_lastValue = _context.lookupReference(ref);
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"));
}
if (!obj || !ast->name) {
_lastValue = 0;
return false;
}
_lastValue = obj->lookupMember(ast->name->asString(), &_context);
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)
{
FunctionBodyCheck bodyCheck;
_messages.append(bodyCheck(ast, _options));
Node::accept(ast->formals, this);
_scopeBuilder.push(ast);
Node::accept(ast->body, this);
_scopeBuilder.pop();
return false;
}
static bool shouldAvoidNonStrictEqualityCheck(ExpressionNode *exp, const Value *other)
{
if (NumericLiteral *literal = cast<NumericLiteral *>(exp)) {
if (literal->value == 0 && !other->asNumberValue())
return true;
} else if ((cast<TrueLiteral *>(exp) || cast<FalseLiteral *>(exp)) && !other->asBooleanValue()) {
return true;
} else if (cast<NullExpression *>(exp)) {
return true;
} else if (IdentifierExpression *ident = cast<IdentifierExpression *>(exp)) {
if (ident->name && ident->name->asString() == QLatin1String("undefined"))
return true;
} else if (StringLiteral *literal = cast<StringLiteral *>(exp)) {
if ((!literal->value || literal->value->asString().isEmpty()) && !other->asStringValue())
return true;
}
return false;
}
bool Check::visit(BinaryExpression *ast)
{
if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) {
bool warn = _options & WarnAllNonStrictEqualityChecks;
if (!warn && _options & WarnDangerousNonStrictEqualityChecks) {
Evaluate eval(&_context);
const Value *lhs = eval(ast->left);
const Value *rhs = eval(ast->right);
warn = shouldAvoidNonStrictEqualityCheck(ast->left, rhs)
|| shouldAvoidNonStrictEqualityCheck(ast->right, lhs);
}
if (warn) {
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)
&& !cast<TryStatement *>(p)
&& !cast<Catch *>(p)
&& !cast<Finally *>(p)
&& !cast<ForStatement *>(p)
&& !cast<ForEachStatement *>(p)
&& !cast<LocalForStatement *>(p)
&& !cast<LocalForEachStatement *>(p)
&& !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"));
}
}
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) {
Node *p = parent();
if (!cast<ForStatement *>(p)
&& !cast<LocalForStatement *>(p)) {
warning(ast->commaToken, tr("avoid comma expressions"));
}
}
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)
|| cast<PostDecrementExpression *>(ast->expression)
|| cast<FunctionExpression *>(ast->expression);
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;
}
}
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;
}
}
if (UiPublicMember *member = cast<UiPublicMember *>(p)) {
if (!cast<Block *>(member->statement)) {
ok = true;
break;
}
}
}
}
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;
}
bool Check::visit(LocalForStatement *ast)
{
if (ast->condition)
checkAssignInCondition(ast->condition);
return true;
}
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;
}
/// When something is changed here, also change ReadingContext::lookupProperty in
/// texttomodelmerger.cpp
/// ### Maybe put this into the context as a helper method.
const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
{
QList<const ObjectValue *> scopeObjects = _context.scopeChain().qmlScopeObjects;
if (scopeObjects.isEmpty())
return 0;
if (! id)
return 0; // ### error?
if (! id->name) // possible after error recovery
return 0;
QString propertyName = id->name->asString();
if (propertyName == QLatin1String("id") && ! id->next)
return 0; // ### should probably be a special value
// attached properties
bool isAttachedProperty = false;
if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
isAttachedProperty = true;
if (const ObjectValue *qmlTypes = _context.scopeChain().qmlTypes)
scopeObjects += qmlTypes;
}
if (scopeObjects.isEmpty())
return 0;
// global lookup for first part of id
const Value *value = 0;
for (int i = scopeObjects.size() - 1; i >= 0; --i) {
value = scopeObjects[i]->lookupMember(propertyName, &_context);
if (value)
break;
}
if (!value) {
error(id->identifierToken,
Check::tr("'%1' is not a valid property name").arg(propertyName));
return 0;
}
// can't look up members for attached properties
if (isAttachedProperty)
return 0;
// resolve references
if (const Reference *ref = value->asReference())
value = _context.lookupReference(ref);
// member lookup
const UiQualifiedId *idPart = id;
while (idPart->next) {
const ObjectValue *objectValue = value_cast<const ObjectValue *>(value);
if (! objectValue) {
error(idPart->identifierToken,
Check::tr("'%1' does not have members").arg(propertyName));
return 0;
}
if (! idPart->next->name) {
// somebody typed "id." and error recovery still gave us a valid tree,
// so just bail out here.
return 0;
}
idPart = idPart->next;
propertyName = idPart->name->asString();
value = objectValue->lookupMember(propertyName, &_context);
if (! value) {
error(idPart->identifierToken,
Check::tr("'%1' is not a member of '%2'").arg(
propertyName, objectValue->className()));
return 0;
}
}
return value;
}
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)
{
// full flow analysis would be neat
if (!statements || !(_options & WarnCaseWithoutFlowControlEnd))
return;
Statement *lastStatement = 0;
for (StatementList *slist = statements; slist; slist = slist->next)
lastStatement = slist->statement;
if (!cast<ReturnStatement *>(lastStatement)
&& !cast<ThrowStatement *>(lastStatement)
&& !cast<BreakStatement *>(lastStatement)
&& !cast<ContinueStatement *>(lastStatement)) {
warning(errorLoc, tr("case does not end with return, break, continue or throw"));
}
}
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));
}
Node *Check::parent(int distance)
{
const int index = _chain.size() - 2 - distance;
if (index < 0)
return 0;
return _chain.at(index);
}