/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (info@qt.nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** 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, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** 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. ** ** 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; 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 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")); validBuiltinPropertyNames.insert(QLatin1String("variant")); validBuiltinPropertyNames.insert(QLatin1String("vector3d")); validBuiltinPropertyNames.insert(QLatin1String("alias")); } QSet<QString> validBuiltinPropertyNames; }; } // anonymous namespace Q_GLOBAL_STATIC(SharedData, sharedData) bool QmlJS::isValidBuiltinPropertyType(const QString &name) { return sharedData()->validBuiltinPropertyNames.contains(name); } namespace { class AssignmentCheck : public ValueVisitor { public: DiagnosticMessage operator()( const Document::Ptr &document, const SourceLocation &location, const Value *lhsValue, const Value *rhsValue, Node *ast) { _doc = document; _message = DiagnosticMessage(DiagnosticMessage::Error, location, QString()); _rhsValue = rhsValue; if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast)) _ast = expStmt->expression; else _ast = ast->expressionCast(); 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<TrueLiteral *>(_ast) || cast<FalseLiteral *>(_ast)) { _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)) || cast<TrueLiteral *>(_ast) || cast<FalseLiteral *>(_ast)) { _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 DeclarationsCheck : 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 (_options & Check::WarnDuplicateDeclaration) { if (_formalParameterNames.contains(name)) { warning(ast->identifierToken, Check::tr("already a formal parameter")); } else if (_declaredFunctions.contains(name)) { warning(ast->identifierToken, Check::tr("already declared as function")); } else if (_declaredVariables.contains(name)) { warning(ast->identifierToken, Check::tr("duplicate declaration")); } } 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 (_options & Check::WarnDuplicateDeclaration) { if (_formalParameterNames.contains(name)) { warning(ast->identifierToken, Check::tr("already a formal parameter")); } else if (_declaredVariables.contains(name)) { warning(ast->identifierToken, Check::tr("already declared as var")); } else if (_declaredFunctions.contains(name)) { warning(ast->identifierToken, Check::tr("duplicate declaration")); } } 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 ContextPtr &context) : _doc(doc) , _context(context) , _scopeChain(doc, _context) , _scopeBuilder(&_scopeChain) , _options(WarnDangerousNonStrictEqualityChecks | WarnBlocks | WarnWith | WarnVoid | WarnCommaExpression | WarnExpressionStatement | WarnAssignInCondition | WarnUseBeforeDeclaration | WarnDuplicateDeclaration | WarnCaseWithoutFlowControlEnd | WarnNonCapitalizedNew | WarnCallsOfCapitalizedFunctions | ErrCheckTypeErrors) , _lastValue(0) { } 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? _scopeChain.setQmlScopeObjects(QList<const ObjectValue *>()); } 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); if (!ast->statement) return false; const Value *lhsValue = checkScopeObjectMember(ast->qualifiedId); if (lhsValue) { Evaluate evaluator(&_scopeChain); const Value *rhsValue = evaluator(ast->statement); 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; } if (Block *block = cast<Block *>(ast->statement)) { DeclarationsCheck 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(UiPublicMember *ast) { // check if the member type is valid if (ast->memberType) { const QString name = ast->memberType->asString(); if (!name.isEmpty() && name.at(0).isLower()) { if (!isValidBuiltinPropertyType(name)) error(ast->typeToken, tr("'%1' is not a valid property type").arg(name)); } } return true; } bool Check::visit(IdentifierExpression *ast) { // currently disabled: too many false negatives return true; _lastValue = 0; if (ast->name) { Evaluate evaluator(&_scopeChain); _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) { DeclarationsCheck 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(&_scopeChain); 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; } static QString functionName(ExpressionNode *ast, SourceLocation *location) { if (IdentifierExpression *id = cast<IdentifierExpression *>(ast)) { if (id->name) { *location = id->identifierToken; return id->name->asString(); } } else if (FieldMemberExpression *fme = cast<FieldMemberExpression *>(ast)) { if (fme->name) { *location = fme->identifierToken; return fme->name->asString(); } } 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")); } } 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; } /// 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 = _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 = _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); }