1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
33 #include "qmljscheck.h"
34 #include "qmljsbind.h"
35 #include "qmljscontext.h"
36 #include "qmljsevaluate.h"
37 #include "qmljsutils.h"
38 #include "parser/qmljsast_p.h"
40 #include <utils/qtcassert.h>
42 #include <QtCore/QDebug>
43 #include <QtCore/QDir>
44 #include <QtGui/QColor>
45 #include <QtGui/QApplication>
47 using namespace QmlJS;
48 using namespace QmlJS::AST;
49 using namespace QmlJS::StaticAnalysis;
53 class AssignmentCheck : public ValueVisitor
57 const Document::Ptr &document,
58 const SourceLocation &location,
59 const Value *lhsValue,
60 const Value *rhsValue,
66 if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast))
67 _ast = expStmt->expression;
69 _ast = ast->expressionCast();
72 lhsValue->accept(this);
77 void setMessage(Type type)
79 _message = Message(type, _location);
82 virtual void visit(const NumberValue *value)
84 if (const QmlEnumValue *enumValue = value_cast<QmlEnumValue>(value)) {
85 if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
86 const QString valueName = stringLiteral->value.toString();
88 if (!enumValue->keys().contains(valueName)) {
89 setMessage(ErrInvalidEnumValue);
91 } else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
92 && ! _rhsValue->asUndefinedValue()) {
93 setMessage(ErrEnumValueMustBeStringOrNumber);
96 if (cast<TrueLiteral *>(_ast)
97 || cast<FalseLiteral *>(_ast)) {
98 setMessage(ErrNumberValueExpected);
103 virtual void visit(const BooleanValue *)
105 UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
107 if (cast<StringLiteral *>(_ast)
108 || cast<NumericLiteral *>(_ast)
109 || (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) {
110 setMessage(ErrBooleanValueExpected);
114 virtual void visit(const StringValue *value)
116 UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
118 if (cast<NumericLiteral *>(_ast)
119 || (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))
120 || cast<TrueLiteral *>(_ast)
121 || cast<FalseLiteral *>(_ast)) {
122 setMessage(ErrStringValueExpected);
125 if (value && value->asUrlValue()) {
126 if (StringLiteral *literal = cast<StringLiteral *>(_ast)) {
127 QUrl url(literal->value.toString());
128 if (!url.isValid() && !url.isEmpty()) {
129 setMessage(ErrInvalidUrl);
131 QString fileName = url.toLocalFile();
132 if (!fileName.isEmpty()) {
133 if (QFileInfo(fileName).isRelative()) {
134 fileName.prepend(QDir::separator());
135 fileName.prepend(_doc->path());
137 if (!QFileInfo(fileName).exists()) {
138 setMessage(WarnFileOrDirectoryDoesNotExist);
146 virtual void visit(const ColorValue *)
148 if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
149 if (!toQColor(stringLiteral->value.toString()).isValid())
150 setMessage(ErrInvalidColor);
152 visit((StringValue *)0);
156 virtual void visit(const AnchorLineValue *)
158 if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUndefinedValue()))
159 setMessage(ErrAnchorLineExpected);
164 SourceLocation _location;
165 const Value *_rhsValue;
166 ExpressionNode *_ast;
169 class ReachesEndCheck : protected Visitor
172 bool operator()(Node *node)
175 _labelledBreaks.clear();
176 return check(node) == ReachesEnd;
180 // Sorted by how much code will be reachable from that state, i.e.
181 // ReachesEnd is guaranteed to reach more code than Break and so on.
190 QHash<QString, Node *> _labels;
191 QSet<Node *> _labelledBreaks;
193 virtual void onUnreachable(Node *)
196 virtual State check(Node *node)
199 Node::accept(node, this);
203 virtual bool preVisit(Node *ast)
205 if (ast->expressionCast())
207 if (_state == ReachesEnd)
209 if (Statement *stmt = ast->statementCast())
211 if (FunctionSourceElement *fun = cast<FunctionSourceElement *>(ast))
212 onUnreachable(fun->declaration);
213 if (StatementSourceElement *stmt = cast<StatementSourceElement *>(ast))
214 onUnreachable(stmt->statement);
218 virtual bool visit(LabelledStatement *ast)
220 // get the target statement
221 Statement *end = ast->statement;
223 if (LabelledStatement *label = cast<LabelledStatement *>(end))
224 end = label->statement;
228 if (!ast->label.isEmpty())
229 _labels[ast->label.toString()] = end;
233 virtual bool visit(BreakStatement *ast)
236 if (!ast->label.isEmpty()) {
237 if (Node *target = _labels.value(ast->label.toString())) {
238 _labelledBreaks.insert(target);
239 _state = ReturnOrThrow; // unwind until label is hit
245 // labelled continues don't change control flow...
246 virtual bool visit(ContinueStatement *) { _state = Continue; return false; }
248 virtual bool visit(ReturnStatement *) { _state = ReturnOrThrow; return false; }
249 virtual bool visit(ThrowStatement *) { _state = ReturnOrThrow; return false; }
251 virtual bool visit(IfStatement *ast)
253 State ok = check(ast->ok);
254 State ko = check(ast->ko);
255 _state = qMin(ok, ko);
259 void handleClause(StatementList *statements, State *result, bool *fallthrough)
261 State clauseResult = check(statements);
262 if (clauseResult == ReachesEnd) {
265 *fallthrough = false;
266 *result = qMin(*result, clauseResult);
270 virtual bool visit(SwitchStatement *ast)
274 State result = ReturnOrThrow;
275 bool lastWasFallthrough = false;
277 for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
279 handleClause(it->clause->statements, &result, &lastWasFallthrough);
281 if (ast->block->defaultClause)
282 handleClause(ast->block->defaultClause->statements, &result, &lastWasFallthrough);
283 for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
285 handleClause(it->clause->statements, &result, &lastWasFallthrough);
288 if (lastWasFallthrough || !ast->block->defaultClause)
290 if (result == Break || _labelledBreaks.contains(ast))
296 virtual bool visit(TryStatement *ast)
298 State tryBody = check(ast->statement);
299 State catchBody = ReturnOrThrow;
300 if (ast->catchExpression)
301 catchBody = check(ast->catchExpression->statement);
302 State finallyBody = ReachesEnd;
303 if (ast->finallyExpression)
304 finallyBody = check(ast->finallyExpression->statement);
306 _state = qMax(qMin(tryBody, catchBody), finallyBody);
310 bool preconditionLoopStatement(Node *, Statement *body)
313 _state = ReachesEnd; // condition could be false...
317 virtual bool visit(WhileStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
318 virtual bool visit(ForStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
319 virtual bool visit(ForEachStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
320 virtual bool visit(LocalForStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
321 virtual bool visit(LocalForEachStatement *ast) { return preconditionLoopStatement(ast, ast->statement); }
323 virtual bool visit(DoWhileStatement *ast)
325 check(ast->statement);
326 // not necessarily an infinite loop due to labelled breaks
327 if (_state == Continue)
328 _state = ReturnOrThrow;
329 if (_state == Break || _labelledBreaks.contains(ast))
335 class MarkUnreachableCode : protected ReachesEndCheck
337 QList<Message> _messages;
338 bool _emittedWarning;
341 QList<Message> operator()(Node *ast)
349 virtual State check(Node *node)
351 bool oldwarning = _emittedWarning;
352 _emittedWarning = false;
353 State s = ReachesEndCheck::check(node);
354 _emittedWarning = oldwarning;
358 virtual void onUnreachable(Node *node)
362 _emittedWarning = true;
364 Message message(WarnUnreachable, SourceLocation());
365 if (Statement *statement = node->statementCast())
366 message.location = locationFromRange(statement->firstSourceLocation(), statement->lastSourceLocation());
367 else if (ExpressionNode *expr = node->expressionCast())
368 message.location = locationFromRange(expr->firstSourceLocation(), expr->lastSourceLocation());
369 if (message.isValid())
370 _messages += message;
374 class DeclarationsCheck : protected Visitor
377 QList<Message> operator()(FunctionExpression *function)
380 for (FormalParameterList *plist = function->formals; plist; plist = plist->next) {
381 if (!plist->name.isEmpty())
382 _formalParameterNames += plist->name.toString();
385 Node::accept(function->body, this);
389 QList<Message> operator()(Node *node)
392 Node::accept(node, this);
400 _declaredFunctions.clear();
401 _declaredVariables.clear();
402 _possiblyUndeclaredUses.clear();
403 _seenNonDeclarationStatement = false;
404 _formalParameterNames.clear();
407 void postVisit(Node *ast)
409 if (!_seenNonDeclarationStatement && ast->statementCast()
410 && !cast<VariableStatement *>(ast)) {
411 _seenNonDeclarationStatement = true;
415 bool visit(IdentifierExpression *ast)
417 if (ast->name.isEmpty())
419 const QString &name = ast->name.toString();
420 if (!_declaredFunctions.contains(name) && !_declaredVariables.contains(name))
421 _possiblyUndeclaredUses[name].append(ast->identifierToken);
425 bool visit(VariableStatement *ast)
427 if (_seenNonDeclarationStatement) {
428 addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->declarationKindToken);
433 bool visit(VariableDeclaration *ast)
435 if (ast->name.isEmpty())
437 const QString &name = ast->name.toString();
439 if (_formalParameterNames.contains(name)) {
440 addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
441 } else if (_declaredFunctions.contains(name)) {
442 addMessage(WarnAlreadyFunction, ast->identifierToken, name);
443 } else if (_declaredVariables.contains(name)) {
444 addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
447 if (_possiblyUndeclaredUses.contains(name)) {
448 foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
449 addMessage(WarnVarUsedBeforeDeclaration, loc, name);
451 _possiblyUndeclaredUses.remove(name);
453 _declaredVariables[name] = ast;
458 bool visit(FunctionDeclaration *ast)
460 if (_seenNonDeclarationStatement) {
461 addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->functionToken);
464 return visit(static_cast<FunctionExpression *>(ast));
467 bool visit(FunctionExpression *ast)
469 if (ast->name.isEmpty())
471 const QString &name = ast->name.toString();
473 if (_formalParameterNames.contains(name)) {
474 addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
475 } else if (_declaredVariables.contains(name)) {
476 addMessage(WarnAlreadyVar, ast->identifierToken, name);
477 } else if (_declaredFunctions.contains(name)) {
478 addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
481 if (FunctionDeclaration *decl = cast<FunctionDeclaration *>(ast)) {
482 if (_possiblyUndeclaredUses.contains(name)) {
483 foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
484 addMessage(WarnFunctionUsedBeforeDeclaration, loc, name);
486 _possiblyUndeclaredUses.remove(name);
488 _declaredFunctions[name] = decl;
495 void addMessage(Type type, const SourceLocation &loc, const QString &arg1 = QString())
497 _messages.append(Message(type, loc, arg1));
500 QList<Message> _messages;
501 QStringList _formalParameterNames;
502 QHash<QString, VariableDeclaration *> _declaredVariables;
503 QHash<QString, FunctionDeclaration *> _declaredFunctions;
504 QHash<QString, QList<SourceLocation> > _possiblyUndeclaredUses;
505 bool _seenNonDeclarationStatement;
508 } // end of anonymous namespace
510 Check::Check(Document::Ptr doc, const ContextPtr &context)
513 , _scopeChain(doc, _context)
514 , _scopeBuilder(&_scopeChain)
518 const Imports *imports = context->imports(doc.data());
519 if (imports && !imports->importFailed())
522 _enabledMessages = Message::allMessageTypes().toSet();
523 disableMessage(HintAnonymousFunctionSpacing);
524 disableMessage(HintDeclareVarsInOneLine);
525 disableMessage(HintDeclarationsShouldBeAtStartOfFunction);
526 disableMessage(HintBinaryOperatorSpacing);
527 disableMessage(HintOneStatementPerLine);
528 disableMessage(HintExtraParentheses);
535 QList<Message> Check::operator()()
538 Node::accept(_doc->ast(), this);
542 void Check::enableMessage(Type type)
544 _enabledMessages.insert(type);
547 void Check::disableMessage(Type type)
549 _enabledMessages.remove(type);
552 bool Check::preVisit(Node *ast)
558 void Check::postVisit(Node *)
563 bool Check::visit(UiProgram *)
568 bool Check::visit(UiObjectInitializer *)
570 m_propertyStack.push(StringSet());
571 UiObjectDefinition *objectDefinition = cast<UiObjectDefinition *>(parent());
572 if (objectDefinition && objectDefinition->qualifiedTypeNameId->name == "Component")
573 m_idStack.push(StringSet());
574 UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
575 if (objectBinding && objectBinding->qualifiedTypeNameId->name == "Component")
576 m_idStack.push(StringSet());
577 if (m_idStack.isEmpty())
578 m_idStack.push(StringSet());
582 void Check::endVisit(UiObjectInitializer *)
584 m_propertyStack.pop();
585 UiObjectDefinition *objectDenition = cast<UiObjectDefinition *>(parent());
586 if (objectDenition && objectDenition->qualifiedTypeNameId->name == "Component")
588 UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
589 if (objectBinding && objectBinding->qualifiedTypeNameId->name == "Component")
593 void Check::checkProperty(UiQualifiedId *qualifiedId)
595 const QString id = toString(qualifiedId);
596 if (id.at(0).isLower()) {
597 if (m_propertyStack.top().contains(id)) {
598 addMessage(ErrPropertiesCanOnlyHaveOneBinding, fullLocationForQualifiedId(qualifiedId));
600 m_propertyStack.top().insert(id);
604 bool Check::visit(UiObjectDefinition *ast)
606 visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
610 bool Check::visit(UiObjectBinding *ast)
612 checkScopeObjectMember(ast->qualifiedId);
613 if (!ast->hasOnToken)
614 checkProperty(ast->qualifiedId);
616 visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
620 void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
621 UiObjectInitializer *initializer)
623 // Don't do type checks if it's a grouped property binding.
624 // For instance: anchors { ... }
625 if (_doc->bind()->isGroupedPropertyBinding(ast)) {
626 checkScopeObjectMember(typeId);
627 // ### don't give up!
631 bool typeError = false;
633 const SourceLocation typeErrorLocation = fullLocationForQualifiedId(typeId);
634 const ObjectValue *prototype = _context->lookupType(_doc.data(), typeId);
637 addMessage(ErrUnknownComponent, typeErrorLocation);
639 PrototypeIterator iter(prototype, _context);
640 QList<const ObjectValue *> prototypes = iter.all();
641 if (iter.error() != PrototypeIterator::NoError)
643 const ObjectValue *lastPrototype = prototypes.last();
644 if (iter.error() == PrototypeIterator::ReferenceResolutionError) {
645 if (const QmlPrototypeReference *ref =
646 value_cast<QmlPrototypeReference>(lastPrototype->prototype())) {
647 addMessage(ErrCouldNotResolvePrototypeOf, typeErrorLocation,
648 toString(ref->qmlTypeName()), lastPrototype->className());
650 addMessage(ErrCouldNotResolvePrototype, typeErrorLocation,
651 lastPrototype->className());
653 } else if (iter.error() == PrototypeIterator::CycleError) {
654 addMessage(ErrPrototypeCycle, typeErrorLocation,
655 lastPrototype->className());
660 _scopeBuilder.push(ast);
663 // suppress subsequent errors about scope object lookup by clearing
664 // the scope object list
665 // ### todo: better way?
666 _scopeChain.setQmlScopeObjects(QList<const ObjectValue *>());
669 Node::accept(initializer, this);
674 bool Check::visit(UiScriptBinding *ast)
676 // special case for id property
677 if (ast->qualifiedId->name == QLatin1String("id") && ! ast->qualifiedId->next) {
678 if (! ast->statement)
681 const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
682 ast->statement->lastSourceLocation());
684 ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
686 addMessage(ErrIdExpected, loc);
691 if (IdentifierExpression *idExp = cast<IdentifierExpression *>(expStmt->expression)) {
692 id = idExp->name.toString();
693 } else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) {
694 id = strExp->value.toString();
695 addMessage(ErrInvalidId, loc);
697 addMessage(ErrIdExpected, loc);
701 if (id.isEmpty() || (!id.at(0).isLower() && id.at(0) != '_')) {
702 addMessage(ErrInvalidId, loc);
706 if (m_idStack.top().contains(id)) {
707 addMessage(ErrDuplicateId, loc);
710 m_idStack.top().insert(id);
713 checkProperty(ast->qualifiedId);
718 const Value *lhsValue = checkScopeObjectMember(ast->qualifiedId);
720 Evaluate evaluator(&_scopeChain);
721 const Value *rhsValue = evaluator(ast->statement);
723 const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
724 ast->statement->lastSourceLocation());
725 AssignmentCheck assignmentCheck;
726 Message message = assignmentCheck(_doc, loc, lhsValue, rhsValue, ast->statement);
727 if (message.isValid())
731 checkBindingRhs(ast->statement);
733 Node::accept(ast->qualifiedId, this);
734 _scopeBuilder.push(ast);
735 Node::accept(ast->statement, this);
741 bool Check::visit(UiArrayBinding *ast)
743 checkScopeObjectMember(ast->qualifiedId);
744 checkProperty(ast->qualifiedId);
749 bool Check::visit(UiPublicMember *ast)
751 // check if the member type is valid
752 if (!ast->memberType.isEmpty()) {
753 const QString &name = ast->memberType.toString();
754 if (!name.isEmpty() && name.at(0).isLower()) {
755 if (!isValidBuiltinPropertyType(name))
756 addMessage(ErrInvalidPropertyType, ast->typeToken, name);
760 checkBindingRhs(ast->statement);
762 _scopeBuilder.push(ast);
763 Node::accept(ast->statement, this);
764 Node::accept(ast->binding, this);
770 bool Check::visit(IdentifierExpression *)
772 // currently disabled: too many false negatives
776 // if (!ast->name.isEmpty()) {
777 // Evaluate evaluator(&_scopeChain);
778 // _lastValue = evaluator.reference(ast);
780 // addMessage(ErrUnknownIdentifier, ast->identifierToken);
781 // if (const Reference *ref = value_cast<Reference>(_lastValue)) {
782 // _lastValue = _context->lookupReference(ref);
784 // error(ast->identifierToken, tr("could not resolve"));
790 bool Check::visit(FieldMemberExpression *)
792 // currently disabled: too many false negatives
795 // Node::accept(ast->base, this);
798 // const ObjectValue *obj = _lastValue->asObjectValue();
800 // error(locationFromRange(ast->base->firstSourceLocation(), ast->base->lastSourceLocation()),
801 // tr("does not have members"));
803 // if (!obj || ast->name.isEmpty()) {
807 // _lastValue = obj->lookupMember(ast->name.toString(), _context);
809 // error(ast->identifierToken, tr("unknown member"));
813 bool Check::visit(FunctionDeclaration *ast)
815 return visit(static_cast<FunctionExpression *>(ast));
818 bool Check::visit(FunctionExpression *ast)
820 if (ast->name.isEmpty()) {
821 SourceLocation locfunc = ast->functionToken;
822 SourceLocation loclparen = ast->lparenToken;
823 if (locfunc.isValid() && loclparen.isValid()
824 && (locfunc.startLine != loclparen.startLine
825 || locfunc.end() + 1 != loclparen.begin())) {
826 addMessage(HintAnonymousFunctionSpacing, locationFromRange(locfunc, loclparen));
830 DeclarationsCheck bodyCheck;
831 addMessages(bodyCheck(ast));
833 MarkUnreachableCode unreachableCheck;
834 addMessages(unreachableCheck(ast->body));
836 Node::accept(ast->formals, this);
837 _scopeBuilder.push(ast);
838 Node::accept(ast->body, this);
843 static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs)
845 // we currently use undefined as a "we don't know" value
846 if (lhs->asUndefinedValue() || rhs->asUndefinedValue())
849 if (lhs->asStringValue() && rhs->asNumberValue())
850 return true; // coerces string to number
852 if (lhs->asObjectValue() && rhs->asNumberValue())
853 return true; // coerces object to primitive
855 if (lhs->asObjectValue() && rhs->asStringValue())
856 return true; // coerces object to primitive
858 if (lhs->asBooleanValue() && !rhs->asBooleanValue())
859 return true; // coerces bool to number
864 bool Check::visit(BinaryExpression *ast)
866 const QString source = _doc->source();
869 SourceLocation op = ast->operatorToken;
870 if ((op.begin() > 0 && !source.at(op.begin() - 1).isSpace())
871 || (int(op.end()) < source.size() && !source.at(op.end()).isSpace())) {
872 addMessage(HintBinaryOperatorSpacing, op);
876 if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) {
877 Evaluate eval(&_scopeChain);
878 const Value *lhsValue = eval(ast->left);
879 const Value *rhsValue = eval(ast->right);
880 if (shouldAvoidNonStrictEqualityCheck(lhsValue, rhsValue)
881 || shouldAvoidNonStrictEqualityCheck(rhsValue, lhsValue)) {
882 addMessage(MaybeWarnEqualityTypeCoercion, ast->operatorToken);
886 // check odd + ++ combinations
887 const QLatin1Char newline('\n');
888 if (ast->op == QSOperator::Add || ast->op == QSOperator::Sub) {
891 if (ast->op == QSOperator::Add) {
892 match = QLatin1Char('+');
893 msg = WarnConfusingPluses;
895 QTC_CHECK(ast->op == QSOperator::Sub);
896 match = QLatin1Char('-');
897 msg = WarnConfusingMinuses;
900 if (int(op.end()) + 1 < source.size()) {
901 const QChar next = source.at(op.end());
902 if (next.isSpace() && next != newline
903 && source.at(op.end() + 1) == match)
904 addMessage(msg, SourceLocation(op.begin(), 3, op.startLine, op.startColumn));
906 if (op.begin() >= 2) {
907 const QChar prev = source.at(op.begin() - 1);
908 if (prev.isSpace() && prev != newline
909 && source.at(op.begin() - 2) == match)
910 addMessage(msg, SourceLocation(op.begin() - 2, 3, op.startLine, op.startColumn - 2));
917 bool Check::visit(Block *ast)
919 if (Node *p = parent()) {
920 if (!cast<UiScriptBinding *>(p)
921 && !cast<UiPublicMember *>(p)
922 && !cast<TryStatement *>(p)
924 && !cast<Finally *>(p)
925 && !cast<ForStatement *>(p)
926 && !cast<ForEachStatement *>(p)
927 && !cast<LocalForStatement *>(p)
928 && !cast<LocalForEachStatement *>(p)
929 && !cast<DoWhileStatement *>(p)
930 && !cast<WhileStatement *>(p)
931 && !cast<IfStatement *>(p)
932 && !cast<SwitchStatement *>(p)
933 && !cast<WithStatement *>(p)) {
934 addMessage(WarnBlock, ast->lbraceToken);
937 && (cast<UiPublicMember *>(p)
938 || cast<UiScriptBinding *>(p))
939 && ast->lbraceToken.startLine == ast->rbraceToken.startLine) {
940 addMessage(WarnUnintentinalEmptyBlock, locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
946 bool Check::visit(WithStatement *ast)
948 addMessage(WarnWith, ast->withToken);
952 bool Check::visit(VoidExpression *ast)
954 addMessage(WarnVoid, ast->voidToken);
958 bool Check::visit(Expression *ast)
960 if (ast->left && ast->right) {
962 if (!cast<ForStatement *>(p)
963 && !cast<LocalForStatement *>(p)) {
964 addMessage(WarnComma, ast->commaToken);
970 bool Check::visit(ExpressionStatement *ast)
972 if (ast->expression) {
973 bool ok = cast<CallExpression *>(ast->expression)
974 || cast<DeleteExpression *>(ast->expression)
975 || cast<PreDecrementExpression *>(ast->expression)
976 || cast<PreIncrementExpression *>(ast->expression)
977 || cast<PostIncrementExpression *>(ast->expression)
978 || cast<PostDecrementExpression *>(ast->expression)
979 || cast<FunctionExpression *>(ast->expression);
980 if (BinaryExpression *binary = cast<BinaryExpression *>(ast->expression)) {
981 switch (binary->op) {
982 case QSOperator::Assign:
983 case QSOperator::InplaceAdd:
984 case QSOperator::InplaceAnd:
985 case QSOperator::InplaceDiv:
986 case QSOperator::InplaceLeftShift:
987 case QSOperator::InplaceRightShift:
988 case QSOperator::InplaceMod:
989 case QSOperator::InplaceMul:
990 case QSOperator::InplaceOr:
991 case QSOperator::InplaceSub:
992 case QSOperator::InplaceURightShift:
993 case QSOperator::InplaceXor:
999 for (int i = 0; Node *p = parent(i); ++i) {
1000 if (UiScriptBinding *binding = cast<UiScriptBinding *>(p)) {
1001 if (!cast<Block *>(binding->statement)) {
1006 if (UiPublicMember *member = cast<UiPublicMember *>(p)) {
1007 if (!cast<Block *>(member->statement)) {
1016 addMessage(WarnConfusingExpressionStatement,
1017 locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
1023 bool Check::visit(IfStatement *ast)
1025 if (ast->expression)
1026 checkAssignInCondition(ast->expression);
1030 bool Check::visit(ForStatement *ast)
1033 checkAssignInCondition(ast->condition);
1037 bool Check::visit(LocalForStatement *ast)
1040 checkAssignInCondition(ast->condition);
1044 bool Check::visit(WhileStatement *ast)
1046 if (ast->expression)
1047 checkAssignInCondition(ast->expression);
1051 bool Check::visit(DoWhileStatement *ast)
1053 if (ast->expression)
1054 checkAssignInCondition(ast->expression);
1058 bool Check::visit(CaseClause *ast)
1060 checkEndsWithControlFlow(ast->statements, ast->caseToken);
1064 bool Check::visit(DefaultClause *ast)
1066 checkEndsWithControlFlow(ast->statements, ast->defaultToken);
1070 static QString functionName(ExpressionNode *ast, SourceLocation *location)
1072 if (IdentifierExpression *id = cast<IdentifierExpression *>(ast)) {
1073 if (!id->name.isEmpty()) {
1074 *location = id->identifierToken;
1075 return id->name.toString();
1077 } else if (FieldMemberExpression *fme = cast<FieldMemberExpression *>(ast)) {
1078 if (!fme->name.isEmpty()) {
1079 *location = fme->identifierToken;
1080 return fme->name.toString();
1086 void Check::checkNewExpression(ExpressionNode *ast)
1088 SourceLocation location;
1089 const QString name = functionName(ast, &location);
1092 if (!name.at(0).isUpper()) {
1093 addMessage(WarnNewWithLowercaseFunction, location);
1097 void Check::checkBindingRhs(Statement *statement)
1102 DeclarationsCheck bodyCheck;
1103 addMessages(bodyCheck(statement));
1105 MarkUnreachableCode unreachableCheck;
1106 addMessages(unreachableCheck(statement));
1109 void Check::checkExtraParentheses(ExpressionNode *expression)
1111 if (NestedExpression *nested = cast<NestedExpression *>(expression)) {
1112 addMessage(HintExtraParentheses, nested->lparenToken);
1116 void Check::addMessages(const QList<Message> &messages)
1118 foreach (const Message &msg, messages)
1122 void Check::addMessage(const Message &message)
1124 if (message.isValid() && _enabledMessages.contains(message.type))
1125 _messages += message;
1128 void Check::addMessage(Type type, const SourceLocation &location, const QString &arg1, const QString &arg2)
1130 addMessage(Message(type, location, arg1, arg2));
1133 bool Check::visit(NewExpression *ast)
1135 checkNewExpression(ast->expression);
1139 bool Check::visit(NewMemberExpression *ast)
1141 checkNewExpression(ast->base);
1143 // check for Number, Boolean, etc constructor usage
1144 if (IdentifierExpression *idExp = cast<IdentifierExpression *>(ast->base)) {
1145 const QStringRef name = idExp->name;
1146 if (name == QLatin1String("Number")) {
1147 addMessage(WarnNumberConstructor, idExp->identifierToken);
1148 } else if (name == QLatin1String("Boolean")) {
1149 addMessage(WarnBooleanConstructor, idExp->identifierToken);
1150 } else if (name == QLatin1String("String")) {
1151 addMessage(WarnStringConstructor, idExp->identifierToken);
1152 } else if (name == QLatin1String("Object")) {
1153 addMessage(WarnObjectConstructor, idExp->identifierToken);
1154 } else if (name == QLatin1String("Array")) {
1156 if (ast->arguments && ast->arguments->expression && !ast->arguments->next) {
1157 Evaluate evaluate(&_scopeChain);
1158 const Value *arg = evaluate(ast->arguments->expression);
1159 if (arg->asNumberValue() || arg->asUndefinedValue())
1163 addMessage(WarnArrayConstructor, idExp->identifierToken);
1164 } else if (name == QLatin1String("Function")) {
1165 addMessage(WarnFunctionConstructor, idExp->identifierToken);
1172 bool Check::visit(CallExpression *ast)
1174 // check for capitalized function name being called
1175 SourceLocation location;
1176 const QString name = functionName(ast->base, &location);
1177 if (!name.isEmpty() && name.at(0).isUpper()
1178 && name != QLatin1String("String")
1179 && name != QLatin1String("Boolean")
1180 && name != QLatin1String("Date")
1181 && name != QLatin1String("Number")
1182 && name != QLatin1String("Object")) {
1183 addMessage(WarnExpectedNewWithUppercaseFunction, location);
1185 if (cast<IdentifierExpression *>(ast->base) && name == QLatin1String("eval"))
1186 addMessage(WarnEval, location);
1190 bool Check::visit(StatementList *ast)
1192 SourceLocation warnStart;
1193 SourceLocation warnEnd;
1194 unsigned currentLine = 0;
1195 for (StatementList *it = ast; it; it = it->next) {
1198 const SourceLocation itLoc = it->statement->firstSourceLocation();
1199 if (itLoc.startLine != currentLine) { // first statement on a line
1200 if (warnStart.isValid())
1201 addMessage(HintOneStatementPerLine, locationFromRange(warnStart, warnEnd));
1202 warnStart = SourceLocation();
1203 currentLine = itLoc.startLine;
1204 } else { // other statements on the same line
1205 if (!warnStart.isValid())
1207 warnEnd = it->statement->lastSourceLocation();
1210 if (warnStart.isValid())
1211 addMessage(HintOneStatementPerLine, locationFromRange(warnStart, warnEnd));
1216 bool Check::visit(ReturnStatement *ast)
1218 checkExtraParentheses(ast->expression);
1222 bool Check::visit(ThrowStatement *ast)
1224 checkExtraParentheses(ast->expression);
1228 bool Check::visit(DeleteExpression *ast)
1230 checkExtraParentheses(ast->expression);
1234 bool Check::visit(TypeOfExpression *ast)
1236 checkExtraParentheses(ast->expression);
1240 /// When something is changed here, also change ReadingContext::lookupProperty in
1241 /// texttomodelmerger.cpp
1242 /// ### Maybe put this into the context as a helper method.
1243 const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
1248 QList<const ObjectValue *> scopeObjects = _scopeChain.qmlScopeObjects();
1249 if (scopeObjects.isEmpty())
1253 return 0; // ### error?
1255 if (id->name.isEmpty()) // possible after error recovery
1258 QString propertyName = id->name.toString();
1260 if (propertyName == QLatin1String("id") && ! id->next)
1261 return 0; // ### should probably be a special value
1263 // attached properties
1264 bool isAttachedProperty = false;
1265 if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
1266 isAttachedProperty = true;
1267 if (const ObjectValue *qmlTypes = _scopeChain.qmlTypes())
1268 scopeObjects += qmlTypes;
1271 if (scopeObjects.isEmpty())
1274 // global lookup for first part of id
1275 const Value *value = 0;
1276 for (int i = scopeObjects.size() - 1; i >= 0; --i) {
1277 value = scopeObjects[i]->lookupMember(propertyName, _context);
1282 addMessage(ErrInvalidPropertyName, id->identifierToken, propertyName);
1286 // can't look up members for attached properties
1287 if (isAttachedProperty)
1290 // resolve references
1291 if (const Reference *ref = value->asReference())
1292 value = _context->lookupReference(ref);
1295 const UiQualifiedId *idPart = id;
1296 while (idPart->next) {
1297 const ObjectValue *objectValue = value_cast<ObjectValue>(value);
1298 if (! objectValue) {
1299 addMessage(ErrDoesNotHaveMembers, idPart->identifierToken, propertyName);
1303 if (idPart->next->name.isEmpty()) {
1304 // somebody typed "id." and error recovery still gave us a valid tree,
1305 // so just bail out here.
1309 idPart = idPart->next;
1310 propertyName = idPart->name.toString();
1312 value = objectValue->lookupMember(propertyName, _context);
1314 addMessage(ErrInvalidMember, idPart->identifierToken, propertyName, objectValue->className());
1322 void Check::checkAssignInCondition(AST::ExpressionNode *condition)
1324 if (BinaryExpression *binary = cast<BinaryExpression *>(condition)) {
1325 if (binary->op == QSOperator::Assign)
1326 addMessage(WarnAssignmentInCondition, binary->operatorToken);
1330 void Check::checkEndsWithControlFlow(StatementList *statements, SourceLocation errorLoc)
1335 ReachesEndCheck check;
1336 if (check(statements)) {
1337 addMessage(WarnCaseWithoutFlowControl, errorLoc);
1341 Node *Check::parent(int distance)
1343 const int index = _chain.size() - 2 - distance;
1346 return _chain.at(index);