QmlJS checks: Add severity and unique id to messages.

Change-Id: I2cded26524c3f64152107e65d740658e3003ffac
Reviewed-on: http://codereview.qt-project.org/5790
Sanity-Review: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@nokia.com>
This commit is contained in:
Christian Kamm
2011-09-28 15:16:00 +02:00
parent f25f1858f0
commit 55713a1514
14 changed files with 663 additions and 325 deletions

View File

@@ -31,7 +31,8 @@ HEADERS += \
$$PWD/qmljsvalueowner.h \ $$PWD/qmljsvalueowner.h \
$$PWD/qmljscontext.h \ $$PWD/qmljscontext.h \
$$PWD/qmljsscopechain.h \ $$PWD/qmljsscopechain.h \
$$PWD/qmljsutils.h $$PWD/qmljsutils.h \
$$PWD/qmljsstaticanalysismessage.h
SOURCES += \ SOURCES += \
$$PWD/qmljsbind.cpp \ $$PWD/qmljsbind.cpp \
@@ -54,7 +55,8 @@ SOURCES += \
$$PWD/qmljsvalueowner.cpp \ $$PWD/qmljsvalueowner.cpp \
$$PWD/qmljscontext.cpp \ $$PWD/qmljscontext.cpp \
$$PWD/qmljsscopechain.cpp \ $$PWD/qmljsscopechain.cpp \
$$PWD/qmljsutils.cpp $$PWD/qmljsutils.cpp \
$$PWD/qmljsstaticanalysismessage.cpp
RESOURCES += \ RESOURCES += \
$$PWD/qmljs.qrc $$PWD/qmljs.qrc

View File

@@ -37,6 +37,8 @@
#include "qmljsutils.h" #include "qmljsutils.h"
#include "parser/qmljsast_p.h" #include "parser/qmljsast_p.h"
#include <utils/qtcassert.h>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtGui/QColor> #include <QtGui/QColor>
@@ -44,13 +46,14 @@
using namespace QmlJS; using namespace QmlJS;
using namespace QmlJS::AST; using namespace QmlJS::AST;
using namespace QmlJS::StaticAnalysis;
namespace { namespace {
class AssignmentCheck : public ValueVisitor class AssignmentCheck : public ValueVisitor
{ {
public: public:
DiagnosticMessage operator()( Message operator()(
const Document::Ptr &document, const Document::Ptr &document,
const SourceLocation &location, const SourceLocation &location,
const Value *lhsValue, const Value *lhsValue,
@@ -58,8 +61,8 @@ public:
Node *ast) Node *ast)
{ {
_doc = document; _doc = document;
_message = DiagnosticMessage(DiagnosticMessage::Error, location, QString());
_rhsValue = rhsValue; _rhsValue = rhsValue;
_location = location;
if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast)) if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast))
_ast = expStmt->expression; _ast = expStmt->expression;
else else
@@ -71,6 +74,11 @@ public:
return _message; return _message;
} }
void setMessage(Type type)
{
_message = Message(type, _location);
}
virtual void visit(const NumberValue *value) virtual void visit(const NumberValue *value)
{ {
if (const QmlEnumValue *enumValue = dynamic_cast<const QmlEnumValue *>(value)) { if (const QmlEnumValue *enumValue = dynamic_cast<const QmlEnumValue *>(value)) {
@@ -78,16 +86,16 @@ public:
const QString valueName = stringLiteral->value.toString(); const QString valueName = stringLiteral->value.toString();
if (!enumValue->keys().contains(valueName)) { if (!enumValue->keys().contains(valueName)) {
_message.message = Check::tr("unknown value for enum"); setMessage(ErrInvalidEnumValue);
} }
} else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue() } else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
&& ! _rhsValue->asUndefinedValue()) { && ! _rhsValue->asUndefinedValue()) {
_message.message = Check::tr("enum value is not a string or number"); setMessage(ErrEnumValueMustBeStringOrNumber);
} }
} else { } else {
if (cast<TrueLiteral *>(_ast) if (cast<TrueLiteral *>(_ast)
|| cast<FalseLiteral *>(_ast)) { || cast<FalseLiteral *>(_ast)) {
_message.message = Check::tr("numerical value expected"); setMessage(ErrNumberValueExpected);
} }
} }
} }
@@ -99,7 +107,7 @@ public:
if (cast<StringLiteral *>(_ast) if (cast<StringLiteral *>(_ast)
|| cast<NumericLiteral *>(_ast) || cast<NumericLiteral *>(_ast)
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) { || (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) {
_message.message = Check::tr("boolean value expected"); setMessage(ErrBooleanValueExpected);
} }
} }
@@ -111,14 +119,14 @@ public:
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression)) || (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))
|| cast<TrueLiteral *>(_ast) || cast<TrueLiteral *>(_ast)
|| cast<FalseLiteral *>(_ast)) { || cast<FalseLiteral *>(_ast)) {
_message.message = Check::tr("string value expected"); setMessage(ErrStringValueExpected);
} }
if (value && value->asUrlValue()) { if (value && value->asUrlValue()) {
if (StringLiteral *literal = cast<StringLiteral *>(_ast)) { if (StringLiteral *literal = cast<StringLiteral *>(_ast)) {
QUrl url(literal->value.toString()); QUrl url(literal->value.toString());
if (!url.isValid() && !url.isEmpty()) { if (!url.isValid() && !url.isEmpty()) {
_message.message = Check::tr("not a valid url"); setMessage(ErrInvalidUrl);
} else { } else {
QString fileName = url.toLocalFile(); QString fileName = url.toLocalFile();
if (!fileName.isEmpty()) { if (!fileName.isEmpty()) {
@@ -127,8 +135,7 @@ public:
fileName.prepend(_doc->path()); fileName.prepend(_doc->path());
} }
if (!QFileInfo(fileName).exists()) { if (!QFileInfo(fileName).exists()) {
_message.message = Check::tr("file or directory does not exist"); setMessage(WarnFileOrDirectoryDoesNotExist);
_message.kind = DiagnosticMessage::Warning;
} }
} }
} }
@@ -140,7 +147,7 @@ public:
{ {
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) { if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
if (!toQColor(stringLiteral->value.toString()).isValid()) if (!toQColor(stringLiteral->value.toString()).isValid())
_message.message = Check::tr("not a valid color"); setMessage(ErrInvalidColor);
} else { } else {
visit((StringValue *)0); visit((StringValue *)0);
} }
@@ -149,11 +156,12 @@ public:
virtual void visit(const AnchorLineValue *) virtual void visit(const AnchorLineValue *)
{ {
if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUndefinedValue())) if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUndefinedValue()))
_message.message = Check::tr("expected anchor line"); setMessage(ErrAnchorLineExpected);
} }
Document::Ptr _doc; Document::Ptr _doc;
DiagnosticMessage _message; Message _message;
SourceLocation _location;
const Value *_rhsValue; const Value *_rhsValue;
ExpressionNode *_ast; ExpressionNode *_ast;
}; };
@@ -326,11 +334,11 @@ protected:
class MarkUnreachableCode : protected ReachesEndCheck class MarkUnreachableCode : protected ReachesEndCheck
{ {
QList<DiagnosticMessage> _messages; QList<Message> _messages;
bool _emittedWarning; bool _emittedWarning;
public: public:
QList<DiagnosticMessage> operator()(Node *ast) QList<Message> operator()(Node *ast)
{ {
_messages.clear(); _messages.clear();
check(ast); check(ast);
@@ -353,12 +361,12 @@ protected:
return; return;
_emittedWarning = true; _emittedWarning = true;
DiagnosticMessage message(DiagnosticMessage::Warning, SourceLocation(), Check::tr("unreachable")); Message message(WarnUnreachable, SourceLocation());
if (Statement *statement = node->statementCast()) if (Statement *statement = node->statementCast())
message.loc = locationFromRange(statement->firstSourceLocation(), statement->lastSourceLocation()); message.location = locationFromRange(statement->firstSourceLocation(), statement->lastSourceLocation());
else if (ExpressionNode *expr = node->expressionCast()) else if (ExpressionNode *expr = node->expressionCast())
message.loc = locationFromRange(expr->firstSourceLocation(), expr->lastSourceLocation()); message.location = locationFromRange(expr->firstSourceLocation(), expr->lastSourceLocation());
if (message.loc.isValid()) if (message.isValid())
_messages += message; _messages += message;
} }
}; };
@@ -366,10 +374,9 @@ protected:
class DeclarationsCheck : protected Visitor class DeclarationsCheck : protected Visitor
{ {
public: public:
QList<DiagnosticMessage> operator()(FunctionExpression *function, Check::Options options) QList<Message> operator()(FunctionExpression *function)
{ {
clear(); clear();
_options = options;
for (FormalParameterList *plist = function->formals; plist; plist = plist->next) { for (FormalParameterList *plist = function->formals; plist; plist = plist->next) {
if (!plist->name.isEmpty()) if (!plist->name.isEmpty())
_formalParameterNames += plist->name.toString(); _formalParameterNames += plist->name.toString();
@@ -379,10 +386,9 @@ public:
return _messages; return _messages;
} }
QList<DiagnosticMessage> operator()(Node *node, Check::Options options) QList<Message> operator()(Node *node)
{ {
clear(); clear();
_options = options;
Node::accept(node, this); Node::accept(node, this);
return _messages; return _messages;
} }
@@ -418,8 +424,8 @@ protected:
bool visit(VariableStatement *ast) bool visit(VariableStatement *ast)
{ {
if (_options & Check::WarnDeclarationsNotStartOfFunction && _seenNonDeclarationStatement) { if (_seenNonDeclarationStatement) {
warning(ast->declarationKindToken, Check::tr("declarations should be at the start of a function")); addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->declarationKindToken);
} }
return true; return true;
} }
@@ -430,21 +436,17 @@ protected:
return true; return true;
const QString &name = ast->name.toString(); const QString &name = ast->name.toString();
if (_options & Check::WarnDuplicateDeclaration) { if (_formalParameterNames.contains(name)) {
if (_formalParameterNames.contains(name)) { addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
warning(ast->identifierToken, Check::tr("already a formal parameter")); } else if (_declaredFunctions.contains(name)) {
} else if (_declaredFunctions.contains(name)) { addMessage(WarnAlreadyFunction, ast->identifierToken, name);
warning(ast->identifierToken, Check::tr("already declared as function")); } else if (_declaredVariables.contains(name)) {
} else if (_declaredVariables.contains(name)) { addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
warning(ast->identifierToken, Check::tr("duplicate declaration"));
}
} }
if (_possiblyUndeclaredUses.contains(name)) { if (_possiblyUndeclaredUses.contains(name)) {
if (_options & Check::WarnUseBeforeDeclaration) { foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) { addMessage(WarnVarUsedBeforeDeclaration, loc, name);
warning(loc, Check::tr("variable is used before being declared"));
}
} }
_possiblyUndeclaredUses.remove(name); _possiblyUndeclaredUses.remove(name);
} }
@@ -455,8 +457,8 @@ protected:
bool visit(FunctionDeclaration *ast) bool visit(FunctionDeclaration *ast)
{ {
if (_options & Check::WarnDeclarationsNotStartOfFunction &&_seenNonDeclarationStatement) { if (_seenNonDeclarationStatement) {
warning(ast->functionToken, Check::tr("declarations should be at the start of a function")); addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->functionToken);
} }
return visit(static_cast<FunctionExpression *>(ast)); return visit(static_cast<FunctionExpression *>(ast));
@@ -468,22 +470,18 @@ protected:
return false; return false;
const QString &name = ast->name.toString(); const QString &name = ast->name.toString();
if (_options & Check::WarnDuplicateDeclaration) { if (_formalParameterNames.contains(name)) {
if (_formalParameterNames.contains(name)) { addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
warning(ast->identifierToken, Check::tr("already a formal parameter")); } else if (_declaredVariables.contains(name)) {
} else if (_declaredVariables.contains(name)) { addMessage(WarnAlreadyVar, ast->identifierToken, name);
warning(ast->identifierToken, Check::tr("already declared as var")); } else if (_declaredFunctions.contains(name)) {
} else if (_declaredFunctions.contains(name)) { addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
warning(ast->identifierToken, Check::tr("duplicate declaration"));
}
} }
if (FunctionDeclaration *decl = cast<FunctionDeclaration *>(ast)) { if (FunctionDeclaration *decl = cast<FunctionDeclaration *>(ast)) {
if (_possiblyUndeclaredUses.contains(name)) { if (_possiblyUndeclaredUses.contains(name)) {
if (_options & Check::WarnUseBeforeDeclaration) { foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) { addMessage(WarnFunctionUsedBeforeDeclaration, loc, name);
warning(loc, Check::tr("function is used before being declared"));
}
} }
_possiblyUndeclaredUses.remove(name); _possiblyUndeclaredUses.remove(name);
} }
@@ -494,13 +492,12 @@ protected:
} }
private: private:
void warning(const SourceLocation &loc, const QString &message) void addMessage(Type type, const SourceLocation &loc, const QString &arg1 = QString())
{ {
_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message)); _messages.append(Message(type, loc, arg1));
} }
Check::Options _options; QList<Message> _messages;
QList<DiagnosticMessage> _messages;
QStringList _formalParameterNames; QStringList _formalParameterNames;
QHash<QString, VariableDeclaration *> _declaredVariables; QHash<QString, VariableDeclaration *> _declaredVariables;
QHash<QString, FunctionDeclaration *> _declaredFunctions; QHash<QString, FunctionDeclaration *> _declaredFunctions;
@@ -510,33 +507,40 @@ private:
} // end of anonymous namespace } // end of anonymous namespace
Check::Check(Document::Ptr doc, const ContextPtr &context) Check::Check(Document::Ptr doc, const ContextPtr &context)
: _doc(doc) : _doc(doc)
, _context(context) , _context(context)
, _scopeChain(doc, _context) , _scopeChain(doc, _context)
, _scopeBuilder(&_scopeChain) , _scopeBuilder(&_scopeChain)
, _options(WarnDangerousNonStrictEqualityChecks | WarnBlocks | WarnWith
| WarnVoid | WarnCommaExpression | WarnExpressionStatement
| WarnAssignInCondition | WarnUseBeforeDeclaration | WarnDuplicateDeclaration
| WarnCaseWithoutFlowControlEnd | WarnNonCapitalizedNew
| WarnCallsOfCapitalizedFunctions | WarnUnreachablecode
| ErrCheckTypeErrors)
, _lastValue(0) , _lastValue(0)
{ {
_enabledMessages = Message::allMessageTypes().toSet();
disableMessage(HintAnonymousFunctionSpacing);
disableMessage(HintDeclareVarsInOneLine);
disableMessage(HintDeclarationsShouldBeAtStartOfFunction);
} }
Check::~Check() Check::~Check()
{ {
} }
QList<DiagnosticMessage> Check::operator()() QList<Message> Check::operator()()
{ {
_messages.clear(); _messages.clear();
Node::accept(_doc->ast(), this); Node::accept(_doc->ast(), this);
return _messages; return _messages;
} }
void Check::enableMessage(Type type)
{
_enabledMessages.insert(type);
}
void Check::disableMessage(Type type)
{
_enabledMessages.remove(type);
}
bool Check::preVisit(Node *ast) bool Check::preVisit(Node *ast)
{ {
_chain.append(ast); _chain.append(ast);
@@ -583,8 +587,7 @@ void Check::checkProperty(UiQualifiedId *qualifiedId)
const QString id = toString(qualifiedId); const QString id = toString(qualifiedId);
if (id.at(0).isLower()) { if (id.at(0).isLower()) {
if (m_propertyStack.top().contains(id)) { if (m_propertyStack.top().contains(id)) {
error(fullLocationForQualifiedId(qualifiedId), addMessage(ErrPropertiesCanOnlyHaveOneBinding, fullLocationForQualifiedId(qualifiedId));
Check::tr("properties can only be assigned once"));
} }
m_propertyStack.top().insert(id); m_propertyStack.top().insert(id);
} }
@@ -622,32 +625,25 @@ void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
const ObjectValue *prototype = _context->lookupType(_doc.data(), typeId); const ObjectValue *prototype = _context->lookupType(_doc.data(), typeId);
if (!prototype) { if (!prototype) {
typeError = true; typeError = true;
if (_options & ErrCheckTypeErrors) addMessage(ErrUnknownComponent, typeErrorLocation);
error(typeErrorLocation,
Check::tr("unknown type"));
} else { } else {
PrototypeIterator iter(prototype, _context); PrototypeIterator iter(prototype, _context);
QList<const ObjectValue *> prototypes = iter.all(); QList<const ObjectValue *> prototypes = iter.all();
if (iter.error() != PrototypeIterator::NoError) if (iter.error() != PrototypeIterator::NoError)
typeError = true; typeError = true;
if (_options & ErrCheckTypeErrors) { const ObjectValue *lastPrototype = prototypes.last();
const ObjectValue *lastPrototype = prototypes.last(); if (iter.error() == PrototypeIterator::ReferenceResolutionError) {
if (iter.error() == PrototypeIterator::ReferenceResolutionError) { if (const QmlPrototypeReference *ref =
if (const QmlPrototypeReference *ref = dynamic_cast<const QmlPrototypeReference *>(lastPrototype->prototype())) {
dynamic_cast<const QmlPrototypeReference *>(lastPrototype->prototype())) { addMessage(ErrCouldNotResolvePrototypeOf, typeErrorLocation,
error(typeErrorLocation, toString(ref->qmlTypeName()), lastPrototype->className());
Check::tr("could not resolve the prototype %1 of %2").arg( } else {
toString(ref->qmlTypeName()), lastPrototype->className())); addMessage(ErrCouldNotResolvePrototype, typeErrorLocation,
} else { lastPrototype->className());
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()));
} }
} else if (iter.error() == PrototypeIterator::CycleError) {
addMessage(ErrPrototypeCycle, typeErrorLocation,
lastPrototype->className());
} }
} }
@@ -677,7 +673,7 @@ bool Check::visit(UiScriptBinding *ast)
ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement); ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) { if (!expStmt) {
error(loc, Check::tr("expected id")); addMessage(ErrIdExpected, loc);
return false; return false;
} }
@@ -686,19 +682,19 @@ bool Check::visit(UiScriptBinding *ast)
id = idExp->name.toString(); id = idExp->name.toString();
} else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) { } else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) {
id = strExp->value.toString(); id = strExp->value.toString();
warning(loc, Check::tr("using string literals for ids is discouraged")); addMessage(ErrInvalidId, loc);
} else { } else {
error(loc, Check::tr("expected id")); addMessage(ErrIdExpected, loc);
return false; return false;
} }
if (id.isEmpty() || (!id.at(0).isLower() && id.at(0) != '_')) { if (id.isEmpty() || (!id.at(0).isLower() && id.at(0) != '_')) {
error(loc, Check::tr("ids must be lower case or start with underscore")); addMessage(ErrInvalidId, loc);
return false; return false;
} }
if (m_idStack.top().contains(id)) { if (m_idStack.top().contains(id)) {
error(loc, Check::tr("ids must be unique")); addMessage(ErrDuplicateId, loc);
return false; return false;
} }
m_idStack.top().insert(id); m_idStack.top().insert(id);
@@ -717,9 +713,9 @@ bool Check::visit(UiScriptBinding *ast)
const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(), const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
ast->statement->lastSourceLocation()); ast->statement->lastSourceLocation());
AssignmentCheck assignmentCheck; AssignmentCheck assignmentCheck;
DiagnosticMessage message = assignmentCheck(_doc, loc, lhsValue, rhsValue, ast->statement); Message message = assignmentCheck(_doc, loc, lhsValue, rhsValue, ast->statement);
if (! message.message.isEmpty()) if (message.isValid())
_messages += message; addMessage(message);
} }
checkBindingRhs(ast->statement); checkBindingRhs(ast->statement);
@@ -747,7 +743,7 @@ bool Check::visit(UiPublicMember *ast)
const QString &name = ast->memberType.toString(); const QString &name = ast->memberType.toString();
if (!name.isEmpty() && name.at(0).isLower()) { if (!name.isEmpty() && name.at(0).isLower()) {
if (!isValidBuiltinPropertyType(name)) if (!isValidBuiltinPropertyType(name))
error(ast->typeToken, tr("'%1' is not a valid property type").arg(name)); addMessage(ErrInvalidPropertyType, ast->typeToken, name);
} }
} }
@@ -761,47 +757,47 @@ bool Check::visit(UiPublicMember *ast)
return false; return false;
} }
bool Check::visit(IdentifierExpression *ast) bool Check::visit(IdentifierExpression *)
{ {
// currently disabled: too many false negatives // currently disabled: too many false negatives
return true; return true;
_lastValue = 0; // _lastValue = 0;
if (!ast->name.isEmpty()) { // if (!ast->name.isEmpty()) {
Evaluate evaluator(&_scopeChain); // Evaluate evaluator(&_scopeChain);
_lastValue = evaluator.reference(ast); // _lastValue = evaluator.reference(ast);
if (!_lastValue) // if (!_lastValue)
error(ast->identifierToken, tr("unknown identifier")); // addMessage(ErrUnknownIdentifier, ast->identifierToken);
if (const Reference *ref = value_cast<const Reference *>(_lastValue)) { // if (const Reference *ref = value_cast<const Reference *>(_lastValue)) {
_lastValue = _context->lookupReference(ref); // _lastValue = _context->lookupReference(ref);
if (!_lastValue) // if (!_lastValue)
error(ast->identifierToken, tr("could not resolve")); // error(ast->identifierToken, tr("could not resolve"));
} // }
} // }
return false; // return false;
} }
bool Check::visit(FieldMemberExpression *ast) bool Check::visit(FieldMemberExpression *)
{ {
// currently disabled: too many false negatives // currently disabled: too many false negatives
return true; return true;
Node::accept(ast->base, this); // Node::accept(ast->base, this);
if (!_lastValue) // if (!_lastValue)
return false; // return false;
const ObjectValue *obj = _lastValue->asObjectValue(); // const ObjectValue *obj = _lastValue->asObjectValue();
if (!obj) { // if (!obj) {
error(locationFromRange(ast->base->firstSourceLocation(), ast->base->lastSourceLocation()), // error(locationFromRange(ast->base->firstSourceLocation(), ast->base->lastSourceLocation()),
tr("does not have members")); // tr("does not have members"));
} // }
if (!obj || ast->name.isEmpty()) { // if (!obj || ast->name.isEmpty()) {
_lastValue = 0; // _lastValue = 0;
return false; // return false;
} // }
_lastValue = obj->lookupMember(ast->name.toString(), _context); // _lastValue = obj->lookupMember(ast->name.toString(), _context);
if (!_lastValue) // if (!_lastValue)
error(ast->identifierToken, tr("unknown member")); // error(ast->identifierToken, tr("unknown member"));
return false; // return false;
} }
bool Check::visit(FunctionDeclaration *ast) bool Check::visit(FunctionDeclaration *ast)
@@ -812,11 +808,10 @@ bool Check::visit(FunctionDeclaration *ast)
bool Check::visit(FunctionExpression *ast) bool Check::visit(FunctionExpression *ast)
{ {
DeclarationsCheck bodyCheck; DeclarationsCheck bodyCheck;
_messages += bodyCheck(ast, _options); addMessages(bodyCheck(ast));
if (_options & WarnUnreachablecode) {
MarkUnreachableCode unreachableCheck; MarkUnreachableCode unreachableCheck;
_messages += unreachableCheck(ast->body); addMessages(unreachableCheck(ast->body));
}
Node::accept(ast->formals, this); Node::accept(ast->formals, this);
_scopeBuilder.push(ast); _scopeBuilder.push(ast);
@@ -849,16 +844,12 @@ static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs
bool Check::visit(BinaryExpression *ast) bool Check::visit(BinaryExpression *ast)
{ {
if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) { if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) {
bool warn = _options & WarnAllNonStrictEqualityChecks; Evaluate eval(&_scopeChain);
if (!warn && _options & WarnDangerousNonStrictEqualityChecks) { const Value *lhs = eval(ast->left);
Evaluate eval(&_scopeChain); const Value *rhs = eval(ast->right);
const Value *lhs = eval(ast->left); if (shouldAvoidNonStrictEqualityCheck(lhs, rhs)
const Value *rhs = eval(ast->right); || shouldAvoidNonStrictEqualityCheck(rhs, lhs)) {
warn = shouldAvoidNonStrictEqualityCheck(lhs, rhs) addMessage(MaybeWarnEqualityTypeCoercion, ast->operatorToken);
|| shouldAvoidNonStrictEqualityCheck(rhs, lhs);
}
if (warn) {
warning(ast->operatorToken, tr("== and != perform type coercion, use === or !== instead to avoid"));
} }
} }
return true; return true;
@@ -867,8 +858,7 @@ bool Check::visit(BinaryExpression *ast)
bool Check::visit(Block *ast) bool Check::visit(Block *ast)
{ {
if (Node *p = parent()) { if (Node *p = parent()) {
if (_options & WarnBlocks if (!cast<UiScriptBinding *>(p)
&& !cast<UiScriptBinding *>(p)
&& !cast<UiPublicMember *>(p) && !cast<UiPublicMember *>(p)
&& !cast<TryStatement *>(p) && !cast<TryStatement *>(p)
&& !cast<Catch *>(p) && !cast<Catch *>(p)
@@ -882,13 +872,12 @@ bool Check::visit(Block *ast)
&& !cast<IfStatement *>(p) && !cast<IfStatement *>(p)
&& !cast<SwitchStatement *>(p) && !cast<SwitchStatement *>(p)
&& !cast<WithStatement *>(p)) { && !cast<WithStatement *>(p)) {
warning(ast->lbraceToken, tr("blocks do not introduce a new scope, avoid")); addMessage(WarnBlock, ast->lbraceToken);
} }
if (!ast->statements if (!ast->statements
&& (cast<UiPublicMember *>(p) && (cast<UiPublicMember *>(p)
|| cast<UiScriptBinding *>(p))) { || cast<UiScriptBinding *>(p))) {
warning(locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()), addMessage(WarnUnintentinalEmptyBlock, ast->firstSourceLocation());
tr("unintentional empty block, use ({}) for empty object literal"));
} }
} }
return true; return true;
@@ -896,25 +885,23 @@ bool Check::visit(Block *ast)
bool Check::visit(WithStatement *ast) bool Check::visit(WithStatement *ast)
{ {
if (_options & WarnWith) addMessage(WarnWith, ast->withToken);
warning(ast->withToken, tr("use of the with statement is not recommended, use a var instead"));
return true; return true;
} }
bool Check::visit(VoidExpression *ast) bool Check::visit(VoidExpression *ast)
{ {
if (_options & WarnVoid) addMessage(WarnVoid, ast->voidToken);
warning(ast->voidToken, tr("use of void is usually confusing and not recommended"));
return true; return true;
} }
bool Check::visit(Expression *ast) bool Check::visit(Expression *ast)
{ {
if (_options & WarnCommaExpression && ast->left && ast->right) { if (ast->left && ast->right) {
Node *p = parent(); Node *p = parent();
if (!cast<ForStatement *>(p) if (!cast<ForStatement *>(p)
&& !cast<LocalForStatement *>(p)) { && !cast<LocalForStatement *>(p)) {
warning(ast->commaToken, tr("avoid comma expressions")); addMessage(WarnComma, ast->commaToken);
} }
} }
return true; return true;
@@ -922,7 +909,7 @@ bool Check::visit(Expression *ast)
bool Check::visit(ExpressionStatement *ast) bool Check::visit(ExpressionStatement *ast)
{ {
if (_options & WarnExpressionStatement && ast->expression) { if (ast->expression) {
bool ok = cast<CallExpression *>(ast->expression) bool ok = cast<CallExpression *>(ast->expression)
|| cast<DeleteExpression *>(ast->expression) || cast<DeleteExpression *>(ast->expression)
|| cast<PreDecrementExpression *>(ast->expression) || cast<PreDecrementExpression *>(ast->expression)
@@ -966,8 +953,8 @@ bool Check::visit(ExpressionStatement *ast)
} }
if (!ok) { if (!ok) {
warning(locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()), addMessage(WarnConfusingExpressionStatement,
tr("expression statements should be assignments, calls or delete expressions only")); locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
} }
} }
return true; return true;
@@ -1038,14 +1025,12 @@ static QString functionName(ExpressionNode *ast, SourceLocation *location)
void Check::checkNewExpression(ExpressionNode *ast) void Check::checkNewExpression(ExpressionNode *ast)
{ {
if (!(_options & WarnNonCapitalizedNew))
return;
SourceLocation location; SourceLocation location;
const QString name = functionName(ast, &location); const QString name = functionName(ast, &location);
if (name.isEmpty()) if (name.isEmpty())
return; return;
if (!name.at(0).isUpper()) { if (!name.at(0).isUpper()) {
warning(location, tr("'new' should only be used with functions that start with an uppercase letter")); addMessage(WarnNewWithLowercaseFunction, location);
} }
} }
@@ -1055,11 +1040,27 @@ void Check::checkBindingRhs(Statement *statement)
return; return;
DeclarationsCheck bodyCheck; DeclarationsCheck bodyCheck;
_messages += bodyCheck(statement, _options); addMessages(bodyCheck(statement));
if (_options & WarnUnreachablecode) {
MarkUnreachableCode unreachableCheck; MarkUnreachableCode unreachableCheck;
_messages += unreachableCheck(statement); addMessages(unreachableCheck(statement));
} }
void Check::addMessages(const QList<Message> &messages)
{
foreach (const Message &msg, messages)
addMessage(msg);
}
void Check::addMessage(const Message &message)
{
if (message.isValid() && _enabledMessages.contains(message.type))
_messages += message;
}
void Check::addMessage(Type type, const SourceLocation &location, const QString &arg1, const QString &arg2)
{
addMessage(Message(type, location, arg1, arg2));
} }
bool Check::visit(NewExpression *ast) bool Check::visit(NewExpression *ast)
@@ -1077,12 +1078,10 @@ bool Check::visit(NewMemberExpression *ast)
bool Check::visit(CallExpression *ast) bool Check::visit(CallExpression *ast)
{ {
// check for capitalized function name being called // check for capitalized function name being called
if (_options & WarnCallsOfCapitalizedFunctions) { SourceLocation location;
SourceLocation location; const QString name = functionName(ast->base, &location);
const QString name = functionName(ast->base, &location); if (!name.isEmpty() && name.at(0).isUpper()) {
if (!name.isEmpty() && name.at(0).isUpper()) { addMessage(WarnExpectedNewWithUppercaseFunction, location);
warning(location, tr("calls of functions that start with an uppercase letter should use 'new'"));
}
} }
return true; return true;
} }
@@ -1126,8 +1125,7 @@ const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
break; break;
} }
if (!value) { if (!value) {
error(id->identifierToken, addMessage(ErrInvalidPropertyName, id->identifierToken, propertyName);
Check::tr("'%1' is not a valid property name").arg(propertyName));
return 0; return 0;
} }
@@ -1144,8 +1142,7 @@ const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
while (idPart->next) { while (idPart->next) {
const ObjectValue *objectValue = value_cast<const ObjectValue *>(value); const ObjectValue *objectValue = value_cast<const ObjectValue *>(value);
if (! objectValue) { if (! objectValue) {
error(idPart->identifierToken, addMessage(ErrDoesNotHaveMembers, idPart->identifierToken, propertyName);
Check::tr("'%1' does not have members").arg(propertyName));
return 0; return 0;
} }
@@ -1160,9 +1157,7 @@ const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
value = objectValue->lookupMember(propertyName, _context); value = objectValue->lookupMember(propertyName, _context);
if (! value) { if (! value) {
error(idPart->identifierToken, addMessage(ErrInvalidMember, idPart->identifierToken, propertyName, objectValue->className());
Check::tr("'%1' is not a member of '%2'").arg(
propertyName, objectValue->className()));
return 0; return 0;
} }
} }
@@ -1172,35 +1167,23 @@ const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
void Check::checkAssignInCondition(AST::ExpressionNode *condition) void Check::checkAssignInCondition(AST::ExpressionNode *condition)
{ {
if (_options & WarnAssignInCondition) { if (BinaryExpression *binary = cast<BinaryExpression *>(condition)) {
if (BinaryExpression *binary = cast<BinaryExpression *>(condition)) { if (binary->op == QSOperator::Assign)
if (binary->op == QSOperator::Assign) addMessage(WarnAssignmentInCondition, binary->operatorToken);
warning(binary->operatorToken, tr("avoid assignments in conditions"));
}
} }
} }
void Check::checkEndsWithControlFlow(StatementList *statements, SourceLocation errorLoc) void Check::checkEndsWithControlFlow(StatementList *statements, SourceLocation errorLoc)
{ {
if (!statements || !(_options & WarnCaseWithoutFlowControlEnd)) if (!statements)
return; return;
ReachesEndCheck check; ReachesEndCheck check;
if (check(statements)) { if (check(statements)) {
warning(errorLoc, tr("case is not terminated and not empty")); addMessage(WarnCaseWithoutFlowControl, errorLoc);
} }
} }
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) Node *Check::parent(int distance)
{ {
const int index = _chain.size() - 2 - distance; const int index = _chain.size() - 2 - distance;

View File

@@ -37,6 +37,7 @@
#include <qmljs/qmljscontext.h> #include <qmljs/qmljscontext.h>
#include <qmljs/qmljsscopebuilder.h> #include <qmljs/qmljsscopebuilder.h>
#include <qmljs/qmljsscopechain.h> #include <qmljs/qmljsscopechain.h>
#include <qmljs/qmljsstaticanalysismessage.h>
#include <qmljs/parser/qmljsastvisitor_p.h> #include <qmljs/parser/qmljsastvisitor_p.h>
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
@@ -57,33 +58,10 @@ public:
Check(Document::Ptr doc, const ContextPtr &context); Check(Document::Ptr doc, const ContextPtr &context);
virtual ~Check(); virtual ~Check();
QList<DiagnosticMessage> operator()(); QList<StaticAnalysis::Message> operator()();
enum Option { void enableMessage(StaticAnalysis::Type type);
WarnDangerousNonStrictEqualityChecks = 1 << 0, void disableMessage(StaticAnalysis::Type type);
WarnAllNonStrictEqualityChecks = 1 << 1,
WarnBlocks = 1 << 2,
WarnWith = 1 << 3,
WarnVoid = 1 << 4,
WarnCommaExpression = 1 << 5,
WarnExpressionStatement = 1 << 6,
WarnAssignInCondition = 1 << 7,
WarnUseBeforeDeclaration = 1 << 8,
WarnDuplicateDeclaration = 1 << 9,
WarnDeclarationsNotStartOfFunction = 1 << 10,
WarnCaseWithoutFlowControlEnd = 1 << 11,
WarnNonCapitalizedNew = 1 << 12,
WarnCallsOfCapitalizedFunctions = 1 << 13,
WarnUnreachablecode = 1 << 14,
ErrCheckTypeErrors = 1 << 15
};
Q_DECLARE_FLAGS(Options, Option)
const Options options() const
{ return _options; }
void setOptions(Options options)
{ _options = options; }
protected: protected:
virtual bool preVisit(AST::Node *ast); virtual bool preVisit(AST::Node *ast);
@@ -130,8 +108,10 @@ private:
void checkNewExpression(AST::ExpressionNode *node); void checkNewExpression(AST::ExpressionNode *node);
void checkBindingRhs(AST::Statement *statement); void checkBindingRhs(AST::Statement *statement);
void warning(const AST::SourceLocation &loc, const QString &message); void addMessages(const QList<StaticAnalysis::Message> &messages);
void error(const AST::SourceLocation &loc, const QString &message); void addMessage(const StaticAnalysis::Message &message);
void addMessage(StaticAnalysis::Type type, const AST::SourceLocation &location,
const QString &arg1 = QString(), const QString &arg2 = QString());
AST::Node *parent(int distance = 0); AST::Node *parent(int distance = 0);
@@ -141,9 +121,8 @@ private:
ScopeChain _scopeChain; ScopeChain _scopeChain;
ScopeBuilder _scopeBuilder; ScopeBuilder _scopeBuilder;
QList<DiagnosticMessage> _messages; QList<StaticAnalysis::Message> _messages;
QSet<StaticAnalysis::Type> _enabledMessages;
Options _options;
const Value *_lastValue; const Value *_lastValue;
QList<AST::Node *> _chain; QList<AST::Node *> _chain;

View File

@@ -0,0 +1,247 @@
/**************************************************************************
**
** 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 "qmljsstaticanalysismessage.h"
#include <utils/qtcassert.h>
#include <QtCore/QCoreApplication>
using namespace QmlJS;
using namespace QmlJS::StaticAnalysis;
namespace {
class StaticAnalysisMessages
{
Q_DECLARE_TR_FUNCTIONS(StaticAnalysisMessages)
public:
class PrototypeMessageData {
public:
Type type;
Severity severity;
QString message;
int placeholders;
};
void newMsg(Type type, Severity severity, const QString &message, int placeholders = 0)
{
PrototypeMessageData prototype;
prototype.type = type;
prototype.severity = severity;
prototype.message = message;
prototype.placeholders = placeholders;
QTC_CHECK(placeholders <= 2);
QTC_ASSERT(!messages.contains(type), return);
messages[type] = prototype;
}
StaticAnalysisMessages();
QHash<Type, PrototypeMessageData> messages;
};
StaticAnalysisMessages::StaticAnalysisMessages()
{
newMsg(ErrInvalidEnumValue, Error,
tr("invalid value for enum"));
newMsg(ErrEnumValueMustBeStringOrNumber, Error,
tr("enum value must be a string or a number"));
newMsg(ErrNumberValueExpected, Error,
tr("number value expected"));
newMsg(ErrBooleanValueExpected, Error,
tr("boolean value expected"));
newMsg(ErrStringValueExpected, Error,
tr("string value expected"));
newMsg(ErrInvalidUrl, Error,
tr("invalid URL"));
newMsg(WarnFileOrDirectoryDoesNotExist, Warning,
tr("file or directory does not exist"));
newMsg(ErrInvalidColor, Error,
tr("invalid color"));
newMsg(ErrAnchorLineExpected, Error,
tr("anchor line expected"));
newMsg(ErrPropertiesCanOnlyHaveOneBinding, Error,
tr("duplicate property binding"));
newMsg(ErrIdExpected, Error,
tr("id expected"));
newMsg(ErrInvalidId, Error,
tr("invalid id"));
newMsg(ErrDuplicateId, Error,
tr("duplicate id"));
newMsg(ErrInvalidPropertyName, Error,
tr("invalid property name '%1'"), 1);
newMsg(ErrDoesNotHaveMembers, Error,
tr("'%1' does not have members"), 1);
newMsg(ErrInvalidMember, Error,
tr("'%1' is not a member of '%2'"), 2);
newMsg(WarnAssignmentInCondition, Warning,
tr("assignment in condition"));
newMsg(WarnCaseWithoutFlowControl, Warning,
tr("unterminated non-empty case block"));
newMsg(WarnEval, Warning,
tr("do not use 'eval'"));
newMsg(WarnUnreachable, Warning,
tr("unreachable"));
newMsg(WarnWith, Warning,
tr("do not use 'with'"));
newMsg(WarnComma, Warning,
tr("do not use comma expressions"));
newMsg(WarnAlreadyFormalParameter, Warning,
tr("'%1' is already a formal parameter"), 1);
newMsg(WarnAlreadyFunction, Warning,
tr("'%1' is already a function"), 1);
newMsg(WarnVarUsedBeforeDeclaration, Warning,
tr("var '%1' is used before its declaration"), 1);
newMsg(WarnAlreadyVar, Warning,
tr("'%1' is already a var"), 1);
newMsg(WarnDuplicateDeclaration, Warning,
tr("'%1' is declared more than once"), 1);
newMsg(WarnFunctionUsedBeforeDeclaration, Warning,
tr("function '%1' is used before its declaration"), 1);
newMsg(WarnBooleanConstructor, Warning,
tr("do not use 'Boolean' as a constructor"));
newMsg(WarnStringConstructor, Warning,
tr("do not use 'String' as a constructor"));
newMsg(WarnObjectConstructor, Warning,
tr("do not use 'Object' as a constructor"));
newMsg(WarnArrayConstructor, Warning,
tr("do not use 'Array' as a constructor"));
newMsg(WarnFunctionConstructor, Warning,
tr("do not use 'Function' as a constructor"));
newMsg(HintAnonymousFunctionSpacing, Hint,
tr("the 'function' keyword and the opening parenthesis should be separated by a single space"));
newMsg(WarnBlock, Warning,
tr("do not use stand-alone blocks"));
newMsg(WarnVoid, Warning,
tr("do not use void expressions"));
newMsg(WarnConfusingPluses, Warning,
tr("confusing pluses"));
newMsg(WarnConfusingPreincrement, Warning,
tr("confusing preincrement"));
newMsg(WarnConfusingMinuses, Warning,
tr("confusing minuses"));
newMsg(WarnConfusingPredecrement, Warning,
tr("confusing predecrement"));
newMsg(HintDeclareVarsInOneLine, Hint,
tr("declare all function vars on a single line"));
// unused
// newMsg(HintExtraParentheses, Hint,
// tr(""));
newMsg(MaybeWarnEqualityTypeCoercion, MaybeWarning,
tr("== and != may perform type coercion, use === or !== to avoid"));
newMsg(WarnConfusingExpressionStatement, Warning,
tr("expression statements should be assignments, calls or delete expressions only"));
newMsg(HintDeclarationsShouldBeAtStartOfFunction, Error,
tr("var declarations should be at the start of a function"));
newMsg(HintOneStatementPerLine, Error,
tr("only use one statement per line"));
newMsg(ErrUnknownComponent, Error,
tr("unknown component"));
newMsg(ErrCouldNotResolvePrototypeOf, Error,
tr("could not resolve the prototype '%1'' of '%2'"), 2);
newMsg(ErrCouldNotResolvePrototype, Error,
tr("could not resolve the prototype '%1'"), 1);
newMsg(ErrPrototypeCycle, Error,
tr("prototype cycle, the last non-repeated component is '%1'"), 1);
newMsg(ErrInvalidPropertyType, Error,
tr("invalid property type '%1'"), 1);
newMsg(WarnEqualityTypeCoercion, Warning,
tr("== and != perform type coercion, use === or !== to avoid"));
newMsg(WarnExpectedNewWithUppercaseFunction, Warning,
tr("calls of functions that start with an uppercase letter should use 'new'"));
newMsg(WarnNewWithLowercaseFunction, Warning,
tr("'new' should only be used with functions that start with an uppercase letter"));
newMsg(WarnNumberConstructor, Warning,
tr("do not use 'Number' as a constructor"));
newMsg(HintBinaryOperatorSpacing, Hint,
tr("use spaces around binary operators"));
newMsg(WarnUnintentinalEmptyBlock, Warning,
tr("unintentional empty block, use ({}) for empty object literal"));
}
} // anonymous namespace
Q_GLOBAL_STATIC(StaticAnalysisMessages, messages)
QList<Type> Message::allMessageTypes()
{
return messages()->messages.keys();
}
Message::Message()
: type(UnknownType), severity(Hint)
{}
Message::Message(Type type, AST::SourceLocation location, const QString &arg1, const QString &arg2)
: location(location), type(type)
{
QTC_ASSERT(messages()->messages.contains(type), return);
const StaticAnalysisMessages::PrototypeMessageData &prototype = messages()->messages.value(type);
severity = prototype.severity;
message = prototype.message;
if (prototype.placeholders == 0) {
if (!arg1.isEmpty() || !arg2.isEmpty())
qWarning() << "StaticAnalysis message" << type << "expects no arguments";
} else if (prototype.placeholders == 1) {
if (arg1.isEmpty() || !arg2.isEmpty())
qWarning() << "StaticAnalysis message" << type << "expects exactly one argument";
message = message.arg(arg1);
} else if (prototype.placeholders == 2) {
if (arg1.isEmpty() || arg2.isEmpty())
qWarning() << "StaticAnalysis message" << type << "expects exactly two arguments";
message = message.arg(arg1, arg2);
}
}
bool Message::isValid() const
{
return type != UnknownType && location.isValid() && !message.isEmpty();
}
DiagnosticMessage Message::toDiagnosticMessage() const
{
DiagnosticMessage diagnostic;
switch (severity) {
case Hint:
case MaybeWarning:
case Warning:
diagnostic.kind = DiagnosticMessage::Warning;
break;
default:
diagnostic.kind = DiagnosticMessage::Error;
break;
}
diagnostic.loc = location;
diagnostic.message = message;
return diagnostic;
}

View File

@@ -0,0 +1,135 @@
/**************************************************************************
**
** 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.
**
**************************************************************************/
#ifndef QMLJS_STATICANALYSIS_QMLJSSTATICANALYSISMESSAGE_H
#define QMLJS_STATICANALYSIS_QMLJSSTATICANALYSISMESSAGE_H
#include "qmljs_global.h"
#include "parser/qmljsengine_p.h"
namespace QmlJS {
namespace StaticAnalysis {
enum Severity
{
Hint, // cosmetic or convention
MaybeWarning, // possibly a warning, insufficient information
Warning, // could cause unintended behavior
MaybeError, // possibly an error, insufficient information
Error // definitely an error
};
enum Type
{
UnknownType = 0,
ErrInvalidEnumValue = 1,
ErrEnumValueMustBeStringOrNumber = 2,
ErrNumberValueExpected = 3,
ErrBooleanValueExpected = 4,
ErrStringValueExpected = 5,
ErrInvalidUrl = 6,
WarnFileOrDirectoryDoesNotExist = 7,
ErrInvalidColor = 8,
ErrAnchorLineExpected = 9,
ErrPropertiesCanOnlyHaveOneBinding = 10,
ErrIdExpected = 11,
ErrInvalidId = 14,
ErrDuplicateId = 15,
ErrInvalidPropertyName = 16,
ErrDoesNotHaveMembers = 17,
ErrInvalidMember = 18,
WarnAssignmentInCondition = 19,
WarnCaseWithoutFlowControl = 20,
WarnEval = 23,
WarnUnreachable = 28,
WarnWith = 29,
WarnComma = 30,
WarnAlreadyFormalParameter = 103,
WarnAlreadyFunction = 104,
WarnVarUsedBeforeDeclaration = 105,
WarnAlreadyVar = 106,
WarnDuplicateDeclaration = 107,
WarnFunctionUsedBeforeDeclaration = 108,
WarnBooleanConstructor = 109,
WarnStringConstructor = 110,
WarnObjectConstructor = 111,
WarnArrayConstructor = 112,
WarnFunctionConstructor = 113,
HintAnonymousFunctionSpacing = 114,
WarnBlock = 115,
WarnVoid = 116,
WarnConfusingPluses = 117,
WarnConfusingPreincrement = 118,
WarnConfusingMinuses = 119,
WarnConfusingPredecrement = 120,
HintDeclareVarsInOneLine = 121,
HintExtraParentheses = 123,
MaybeWarnEqualityTypeCoercion = 126,
WarnConfusingExpressionStatement = 127,
HintDeclarationsShouldBeAtStartOfFunction = 201,
HintOneStatementPerLine = 202,
ErrUnknownComponent = 300,
ErrCouldNotResolvePrototypeOf = 301,
ErrCouldNotResolvePrototype = 302,
ErrPrototypeCycle = 303,
ErrInvalidPropertyType = 304,
WarnEqualityTypeCoercion = 305,
WarnExpectedNewWithUppercaseFunction = 306,
WarnNewWithLowercaseFunction = 307,
WarnNumberConstructor = 308,
HintBinaryOperatorSpacing = 309,
WarnUnintentinalEmptyBlock = 310
};
class QMLJS_EXPORT Message
{
public:
Message();
Message(Type type, AST::SourceLocation location,
const QString &arg1 = QString(),
const QString &arg2 = QString());
static QList<Type> allMessageTypes();
bool isValid() const;
DiagnosticMessage toDiagnosticMessage() const;
AST::SourceLocation location;
QString message;
Type type;
Severity severity;
};
} // namespace StaticAnalysis
} // namespace QmlJS
#endif // QMLJS_STATICANALYSIS_QMLJSSTATICANALYSISMESSAGE_H

View File

@@ -765,10 +765,13 @@ bool TextToModelMerger::load(const QString &data, DifferenceHandler &differenceH
if (view()->checkSemanticErrors()) { if (view()->checkSemanticErrors()) {
Check check(doc, m_scopeChain->context()); Check check(doc, m_scopeChain->context());
check.setOptions(check.options() & ~Check::ErrCheckTypeErrors); check.disableMessage(StaticAnalysis::ErrUnknownComponent);
foreach (const QmlJS::DiagnosticMessage &diagnosticMessage, check()) { check.disableMessage(StaticAnalysis::ErrPrototypeCycle);
if (diagnosticMessage.isError()) check.disableMessage(StaticAnalysis::ErrCouldNotResolvePrototype);
errors.append(RewriterView::Error(diagnosticMessage, QUrl::fromLocalFile(doc->fileName()))); check.disableMessage(StaticAnalysis::ErrCouldNotResolvePrototypeOf);
foreach (const StaticAnalysis::Message &message, check()) {
if (message.severity == StaticAnalysis::Error)
errors.append(RewriterView::Error(message.toDiagnosticMessage(), QUrl::fromLocalFile(doc->fileName())));
} }
if (!errors.isEmpty()) { if (!errors.isEmpty()) {

View File

@@ -147,7 +147,9 @@ SemanticInfo SemanticInfoUpdater::semanticInfo(const SemanticInfoUpdaterSource &
semanticInfo.m_rootScopeChain = QSharedPointer<const QmlJS::ScopeChain>(scopeChain); semanticInfo.m_rootScopeChain = QSharedPointer<const QmlJS::ScopeChain>(scopeChain);
QmlJS::Check checker(doc, semanticInfo.context); QmlJS::Check checker(doc, semanticInfo.context);
semanticInfo.semanticMessages.append(checker()); foreach (const QmlJS::StaticAnalysis::Message &msg, checker()) {
semanticInfo.semanticMessages += msg.toDiagnosticMessage();
}
return semanticInfo; return semanticInfo;
} }

View File

@@ -87,6 +87,14 @@ static QList<ProjectExplorer::Task> convertToTasks(const QList<DiagnosticMessage
return result; return result;
} }
static QList<ProjectExplorer::Task> convertToTasks(const QList<StaticAnalysis::Message> &messages, const QString &fileName, const QString &category)
{
QList<DiagnosticMessage> diagnostics;
foreach (const StaticAnalysis::Message &msg, messages)
diagnostics += msg.toDiagnosticMessage();
return convertToTasks(diagnostics, fileName, category);
}
void QmlTaskManager::collectMessages( void QmlTaskManager::collectMessages(
QFutureInterface<FileErrorMessages> &future, QFutureInterface<FileErrorMessages> &future,
Snapshot snapshot, QList<ModelManagerInterface::ProjectInfo> projectInfos, Snapshot snapshot, QList<ModelManagerInterface::ProjectInfo> projectInfos,

View File

@@ -1,8 +1,8 @@
import Qt 4.7 import Qt 4.7
// DEFAULTMSG == and != perform type coercion, use === or !== instead to avoid
Rectangle { Rectangle {
onXChanged: { onXChanged: {
if (0 == undefined) {} // W 15 16 if (0 == undefined) {} // 126 15 16
} }
function foo() { function foo() {
@@ -14,47 +14,47 @@ Rectangle {
var o = {} var o = {}
if (s == s) {} if (s == s) {}
if (s == n) {} // W 15 16 if (s == n) {} // 126 15 16
if (s == N) {} // ### should warn: always false if (s == N) {} // ### should warn: always false
if (s == u) {} // W 15 16 if (s == u) {} // 126 15 16
if (s == b) {} // W 15 16 if (s == b) {} // 126 15 16
if (s == o) {} // W 15 16 if (s == o) {} // 126 15 16
if (n == s) {} // W 15 16 if (n == s) {} // 126 15 16
if (n == n) {} if (n == n) {}
if (n == N) {} // ### should warn: always false if (n == N) {} // ### should warn: always false
if (n == u) {} // W 15 16 if (n == u) {} // 126 15 16
if (n == b) {} // W 15 16 if (n == b) {} // 126 15 16
if (n == o) {} // W 15 16 if (n == o) {} // 126 15 16
if (N == s) {} // ### should warn: always false if (N == s) {} // ### should warn: always false
if (N == n) {} // ### should warn: always false if (N == n) {} // ### should warn: always false
if (N == N) {} if (N == N) {}
if (N == u) {} // W 15 16 if (N == u) {} // 126 15 16
// ### should warn: always false // ### should warn: always false
if (N == b) {} // W 15 16 if (N == b) {} // 126 15 16
if (N == o) {} // ### should warn: always false if (N == o) {} // ### should warn: always false
if (u == s) {} // W 15 16 if (u == s) {} // 126 15 16
if (u == n) {} // W 15 16 if (u == n) {} // 126 15 16
if (u == N) {} // W 15 16 if (u == N) {} // 126 15 16
if (u == u) {} // W 15 16 if (u == u) {} // 126 15 16
if (u == b) {} // W 15 16 if (u == b) {} // 126 15 16
if (u == o) {} // W 15 16 if (u == o) {} // 126 15 16
if (b == s) {} // W 15 16 if (b == s) {} // 126 15 16
if (b == n) {} // W 15 16 if (b == n) {} // 126 15 16
// ### should warn: always false // ### should warn: always false
if (b == N) {} // W 15 16 if (b == N) {} // 126 15 16
if (b == u) {} // W 15 16 if (b == u) {} // 126 15 16
if (b == b) {} if (b == b) {}
if (b == o) {} // W 15 16 if (b == o) {} // 126 15 16
if (o == s) {} // W 15 16 if (o == s) {} // 126 15 16
if (o == n) {} // W 15 16 if (o == n) {} // 126 15 16
if (o == N) {} // ### should warn: always false if (o == N) {} // ### should warn: always false
if (o == u) {} // W 15 16 if (o == u) {} // 126 15 16
if (o == b) {} // W 15 16 if (o == b) {} // 126 15 16
if (o == o) {} if (o == o) {}
} }
} }

View File

@@ -1,13 +1,13 @@
import Qt 4.7 import Qt 4.7
// DEFAULTMSG expression statements should be assignments, calls or delete expressions only
Rectangle { Rectangle {
function foo() { function foo() {
a // W 9 9 a // 127 9 9
a + b // W 9 13 a + b // 127 9 13
a() a()
delete b delete b
a = 12 a = 12
a += 12 a += 12
d().foo // W 9 15 d().foo // 127 9 15
} }
} }

View File

@@ -1,17 +1,15 @@
import Qt 4.7 import Qt 4.7
Item { Item {
// DEFAULTMSG 'new' should only be used with functions that start with an uppercase letter
function foo() { function foo() {
a = new A a = new A
a = new A() a = new A()
a = new a // W 17 17 a = new a // 307 17 17
a = new a() // W 17 17 a = new a() // 307 17 17
} }
// DEFAULTMSG calls of functions that start with an uppercase letter should use 'new'
function foo() { function foo() {
a = A() // W 13 13 a = A() // 306 13 13
a = a() a = a()
} }
} }

View File

@@ -51,6 +51,7 @@
using namespace QmlJS; using namespace QmlJS;
using namespace QmlJS::AST; using namespace QmlJS::AST;
using namespace QmlJS::StaticAnalysis;
class tst_Check : public QObject class tst_Check : public QObject
{ {
@@ -89,9 +90,9 @@ void tst_Check::initTestCase()
CppQmlTypesLoader::defaultQtObjects = CppQmlTypesLoader::loadQmlTypes(QFileInfoList() << builtins, &errors, &warnings); CppQmlTypesLoader::defaultQtObjects = CppQmlTypesLoader::loadQmlTypes(QFileInfoList() << builtins, &errors, &warnings);
} }
static bool offsetComparator(DiagnosticMessage lhs, DiagnosticMessage rhs) static bool offsetComparator(const Message &lhs, const Message &rhs)
{ {
return lhs.loc.offset < rhs.loc.offset; return lhs.location.offset < rhs.location.offset;
} }
#define QCOMPARE_NOEXIT(actual, expected) \ #define QCOMPARE_NOEXIT(actual, expected) \
@@ -127,72 +128,52 @@ void tst_Check::test()
ContextPtr context = Link(snapshot, QStringList(), LibraryInfo())(); ContextPtr context = Link(snapshot, QStringList(), LibraryInfo())();
Check checker(doc, context); Check checker(doc, context);
QList<DiagnosticMessage> messages = checker(); QList<Message> messages = checker();
std::sort(messages.begin(), messages.end(), &offsetComparator); std::sort(messages.begin(), messages.end(), &offsetComparator);
const QRegExp errorPattern(" E (\\d+) (\\d+)(.*)"); const QRegExp messagePattern(" (\\d+) (\\d+) (\\d+)");
const QRegExp warningPattern(" W (\\d+) (\\d+)(.*)");
const QRegExp defaultmsgPattern(" DEFAULTMSG (.*)");
QList<DiagnosticMessage> expectedMessages; QList<Message> expectedMessages;
QString defaultMessage;
foreach (const AST::SourceLocation &comment, doc->engine()->comments()) { foreach (const AST::SourceLocation &comment, doc->engine()->comments()) {
const QString text = doc->source().mid(comment.begin(), comment.end() - comment.begin()); const QString text = doc->source().mid(comment.begin(), comment.end() - comment.begin());
if (defaultmsgPattern.indexIn(text) != -1) { if (messagePattern.indexIn(text) == -1)
defaultMessage = defaultmsgPattern.cap(1);
continue; continue;
} const int type = messagePattern.cap(1).toInt();
const int columnStart = messagePattern.cap(2).toInt();
const int columnEnd = messagePattern.cap(3).toInt() + 1;
const QRegExp *match = 0; Message message;
DiagnosticMessage::Kind kind = DiagnosticMessage::Error; message.location = SourceLocation(
if (errorPattern.indexIn(text) != -1) { comment.offset - comment.startColumn + columnStart,
match = &errorPattern; columnEnd - columnStart,
} else if (warningPattern.indexIn(text) != -1) { comment.startLine,
kind = DiagnosticMessage::Warning; columnStart),
match = &warningPattern; message.type = static_cast<Type>(type);
} expectedMessages += message;
if (!match)
continue;
const int columnStart = match->cap(1).toInt();
const int columnEnd = match->cap(2).toInt() + 1;
QString message = match->cap(3);
if (message.isEmpty())
message = defaultMessage;
else
message = message.mid(1);
expectedMessages += DiagnosticMessage(
kind,
SourceLocation(
comment.offset - comment.startColumn + columnStart,
columnEnd - columnStart,
comment.startLine,
columnStart),
message);
} }
for (int i = 0; i < messages.size(); ++i) { for (int i = 0; i < messages.size(); ++i) {
DiagnosticMessage expected; Message expected;
if (i < expectedMessages.size()) if (i < expectedMessages.size())
expected = expectedMessages.at(i); expected = expectedMessages.at(i);
DiagnosticMessage actual = messages.at(i); Message actual = messages.at(i);
bool fail = false; bool fail = false;
fail |= !QCOMPARE_NOEXIT(actual.message, expected.message); fail |= !QCOMPARE_NOEXIT(actual.location.startLine, expected.location.startLine);
fail |= !QCOMPARE_NOEXIT(actual.loc.startLine, expected.loc.startLine);
if (fail) if (fail)
return; return;
fail |= !QCOMPARE_NOEXIT(actual.kind, expected.kind); fail |= !QCOMPARE_NOEXIT(actual.type, expected.type);
fail |= !QCOMPARE_NOEXIT(actual.loc.startColumn, expected.loc.startColumn); fail |= !QCOMPARE_NOEXIT(actual.location.startColumn, expected.location.startColumn);
fail |= !QCOMPARE_NOEXIT(actual.loc.offset, expected.loc.offset); fail |= !QCOMPARE_NOEXIT(actual.location.offset, expected.location.offset);
fail |= !QCOMPARE_NOEXIT(actual.loc.length, expected.loc.length); fail |= !QCOMPARE_NOEXIT(actual.location.length, expected.location.length);
if (fail) { if (fail) {
qDebug() << "Failed for message on line" << actual.loc.startLine << actual.message; qDebug() << "Failed for message on line" << actual.location.startLine << actual.message;
return; return;
} }
} }
if (expectedMessages.size() > messages.size()) { if (expectedMessages.size() > messages.size()) {
DiagnosticMessage missingMessage = expectedMessages.at(messages.size()); Message missingMessage = expectedMessages.at(messages.size());
qDebug() << "expected more messages: " << missingMessage.loc.startLine << missingMessage.message; qDebug() << "expected more messages: " << missingMessage.location.startLine << missingMessage.message;
QFAIL("more messages expected"); QFAIL("more messages expected");
} }
} }

View File

@@ -3,13 +3,13 @@ import Qt 4.7
Item { Item {
function foo() { function foo() {
return return
x() // W 9 11 x() // 28 9 11
x() x()
} }
function foo() { function foo() {
throw new Object() throw new Object()
x() // W 9 11 x() // 28 9 11
x() x()
} }
@@ -26,14 +26,14 @@ Item {
return return
else else
return return
x() // W 9 11 x() // 28 9 11
} }
function foo() { function foo() {
try { try {
throw 1 throw 1
} finally {} } finally {}
x() // W 9 11 x() // 28 9 11
} }
function foo() { function foo() {
@@ -41,7 +41,7 @@ Item {
} finally { } finally {
return return
} }
x() // W 9 11 x() // 28 9 11
} }
function foo() { function foo() {
@@ -60,7 +60,7 @@ Item {
} catch(a) { } catch(a) {
return return
} }
x() // W 9 11 x() // 28 9 11
} }
function foo() { function foo() {
@@ -85,7 +85,7 @@ Item {
default: default:
return return
} }
x() // W 9 11 x() // 28 9 11
} }
function foo() { function foo() {
@@ -97,7 +97,7 @@ Item {
l3: do { l3: do {
break l1 break l1
} while (b); } while (b);
x() // W 13 15 x() // 28 13 15
} while (a); } while (a);
x() // reachable via break x() // reachable via break
} }

View File

@@ -1,17 +1,17 @@
import Qt 4.7 import Qt 4.7
// DEFAULTMSG blocks do not introduce a new scope, avoid
Rectangle { Rectangle {
function foo() { function foo() {
{} // W 9 9 {} // 115 9 9
if (a) {} if (a) {}
} }
onXChanged: { onXChanged: {
{} // W 9 9 {} // 115 9 9
while (A) {} while (A) {}
} }
property int d: { property int d: {
{} // W 9 9 {} // 115 9 9
} }
} }