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/qmljscontext.h \
$$PWD/qmljsscopechain.h \
$$PWD/qmljsutils.h
$$PWD/qmljsutils.h \
$$PWD/qmljsstaticanalysismessage.h
SOURCES += \
$$PWD/qmljsbind.cpp \
@@ -54,7 +55,8 @@ SOURCES += \
$$PWD/qmljsvalueowner.cpp \
$$PWD/qmljscontext.cpp \
$$PWD/qmljsscopechain.cpp \
$$PWD/qmljsutils.cpp
$$PWD/qmljsutils.cpp \
$$PWD/qmljsstaticanalysismessage.cpp
RESOURCES += \
$$PWD/qmljs.qrc

View File

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

View File

@@ -37,6 +37,7 @@
#include <qmljs/qmljscontext.h>
#include <qmljs/qmljsscopebuilder.h>
#include <qmljs/qmljsscopechain.h>
#include <qmljs/qmljsstaticanalysismessage.h>
#include <qmljs/parser/qmljsastvisitor_p.h>
#include <QtCore/QCoreApplication>
@@ -57,33 +58,10 @@ public:
Check(Document::Ptr doc, const ContextPtr &context);
virtual ~Check();
QList<DiagnosticMessage> operator()();
QList<StaticAnalysis::Message> operator()();
enum Option {
WarnDangerousNonStrictEqualityChecks = 1 << 0,
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; }
void enableMessage(StaticAnalysis::Type type);
void disableMessage(StaticAnalysis::Type type);
protected:
virtual bool preVisit(AST::Node *ast);
@@ -130,8 +108,10 @@ private:
void checkNewExpression(AST::ExpressionNode *node);
void checkBindingRhs(AST::Statement *statement);
void warning(const AST::SourceLocation &loc, const QString &message);
void error(const AST::SourceLocation &loc, const QString &message);
void addMessages(const QList<StaticAnalysis::Message> &messages);
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);
@@ -141,9 +121,8 @@ private:
ScopeChain _scopeChain;
ScopeBuilder _scopeBuilder;
QList<DiagnosticMessage> _messages;
Options _options;
QList<StaticAnalysis::Message> _messages;
QSet<StaticAnalysis::Type> _enabledMessages;
const Value *_lastValue;
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