2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** Commercial License Usage
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-15 14:58:39 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2010-02-16 10:36:09 +01:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2010-12-17 16:01:08 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
#include "qmljscheck.h"
|
|
|
|
#include "qmljsbind.h"
|
2010-02-16 11:53:21 +01:00
|
|
|
#include "qmljsevaluate.h"
|
2011-10-07 14:04:06 +02:00
|
|
|
#include "qmljsutils.h"
|
2010-02-16 10:36:09 +01:00
|
|
|
#include "parser/qmljsast_p.h"
|
|
|
|
|
2019-07-03 18:34:30 +02:00
|
|
|
#include <utils/algorithm.h>
|
2011-09-28 15:16:00 +02:00
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
2015-03-04 16:46:23 +01:00
|
|
|
#include <QColor>
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QDir>
|
2020-06-18 19:04:58 +02:00
|
|
|
#include <QRegularExpression>
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
using namespace QmlJS;
|
|
|
|
using namespace QmlJS::AST;
|
2011-09-28 15:16:00 +02:00
|
|
|
using namespace QmlJS::StaticAnalysis;
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-23 17:02:50 +01:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
class AssignmentCheck : public ValueVisitor
|
|
|
|
{
|
|
|
|
public:
|
2011-09-28 15:16:00 +02:00
|
|
|
Message operator()(
|
2010-11-23 14:30:23 +01:00
|
|
|
const Document::Ptr &document,
|
2010-02-23 17:02:50 +01:00
|
|
|
const SourceLocation &location,
|
2011-08-08 12:47:49 +02:00
|
|
|
const Value *lhsValue,
|
|
|
|
const Value *rhsValue,
|
2011-09-01 15:04:21 +02:00
|
|
|
Node *ast)
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
2010-11-23 14:30:23 +01:00
|
|
|
_doc = document;
|
2010-02-23 17:02:50 +01:00
|
|
|
_rhsValue = rhsValue;
|
2011-09-28 15:16:00 +02:00
|
|
|
_location = location;
|
2011-09-01 15:04:21 +02:00
|
|
|
if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast))
|
|
|
|
_ast = expStmt->expression;
|
|
|
|
else
|
|
|
|
_ast = ast->expressionCast();
|
2010-02-23 17:02:50 +01:00
|
|
|
|
|
|
|
if (lhsValue)
|
|
|
|
lhsValue->accept(this);
|
|
|
|
|
|
|
|
return _message;
|
|
|
|
}
|
|
|
|
|
2019-10-04 16:11:02 +02:00
|
|
|
void setMessage(StaticAnalysis::Type type)
|
2011-09-28 15:16:00 +02:00
|
|
|
{
|
|
|
|
_message = Message(type, _location);
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
void visit(const NumberValue *value) override
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
2011-10-10 10:55:37 +02:00
|
|
|
if (const QmlEnumValue *enumValue = value_cast<QmlEnumValue>(value)) {
|
2010-05-19 12:23:55 +02:00
|
|
|
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString valueName = stringLiteral->value.toString();
|
2010-05-19 12:23:55 +02:00
|
|
|
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (!enumValue->keys().contains(valueName))
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrInvalidEnumValue);
|
2010-11-29 08:59:54 +01:00
|
|
|
} else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
|
2011-10-10 12:53:28 +02:00
|
|
|
&& ! _rhsValue->asUnknownValue()) {
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrEnumValueMustBeStringOrNumber);
|
2010-05-19 12:23:55 +02:00
|
|
|
}
|
|
|
|
} else {
|
2011-09-01 15:04:21 +02:00
|
|
|
if (cast<TrueLiteral *>(_ast)
|
|
|
|
|| cast<FalseLiteral *>(_ast)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrNumberValueExpected);
|
2010-05-19 12:23:55 +02:00
|
|
|
}
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
void visit(const BooleanValue *) override
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
|
|
|
UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
|
|
|
|
|
|
|
|
if (cast<StringLiteral *>(_ast)
|
|
|
|
|| cast<NumericLiteral *>(_ast)
|
|
|
|
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))) {
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrBooleanValueExpected);
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
void visit(const StringValue *value) override
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
|
|
|
UnaryMinusExpression *unaryMinus = cast<UnaryMinusExpression *>(_ast);
|
|
|
|
|
|
|
|
if (cast<NumericLiteral *>(_ast)
|
|
|
|
|| (unaryMinus && cast<NumericLiteral *>(unaryMinus->expression))
|
2011-09-01 15:04:21 +02:00
|
|
|
|| cast<TrueLiteral *>(_ast)
|
|
|
|
|| cast<FalseLiteral *>(_ast)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrStringValueExpected);
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
2010-11-23 14:30:23 +01:00
|
|
|
|
|
|
|
if (value && value->asUrlValue()) {
|
|
|
|
if (StringLiteral *literal = cast<StringLiteral *>(_ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
QUrl url(literal->value.toString());
|
2010-11-23 14:30:23 +01:00
|
|
|
if (!url.isValid() && !url.isEmpty()) {
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrInvalidUrl);
|
2010-11-23 14:30:23 +01:00
|
|
|
} else {
|
|
|
|
QString fileName = url.toLocalFile();
|
|
|
|
if (!fileName.isEmpty()) {
|
2019-01-17 15:24:16 +01:00
|
|
|
if (QFileInfo(fileName).isRelative())
|
|
|
|
fileName = QString("/%1%2").arg(_doc->path(), fileName);
|
2017-04-13 16:26:38 +02:00
|
|
|
if (!QFileInfo::exists(fileName))
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(WarnFileOrDirectoryDoesNotExist);
|
2010-11-23 14:30:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
void visit(const ColorValue *) override
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
|
|
|
if (StringLiteral *stringLiteral = cast<StringLiteral *>(_ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!toQColor(stringLiteral->value.toString()).isValid())
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrInvalidColor);
|
2010-02-23 17:02:50 +01:00
|
|
|
} else {
|
2019-01-17 15:24:16 +01:00
|
|
|
static const StringValue *nullStringValue = nullptr;
|
|
|
|
visit(nullStringValue);
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
void visit(const AnchorLineValue *) override
|
2010-02-23 17:02:50 +01:00
|
|
|
{
|
2011-10-10 12:53:28 +02:00
|
|
|
if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUnknownValue()))
|
2011-09-28 15:16:00 +02:00
|
|
|
setMessage(ErrAnchorLineExpected);
|
2010-02-23 17:02:50 +01:00
|
|
|
}
|
|
|
|
|
2010-11-23 14:30:23 +01:00
|
|
|
Document::Ptr _doc;
|
2011-09-28 15:16:00 +02:00
|
|
|
Message _message;
|
|
|
|
SourceLocation _location;
|
2010-02-23 17:02:50 +01:00
|
|
|
const Value *_rhsValue;
|
|
|
|
ExpressionNode *_ast;
|
|
|
|
};
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
class ReachesEndCheck : protected Visitor
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
bool operator()(Node *node)
|
|
|
|
{
|
|
|
|
_labels.clear();
|
|
|
|
_labelledBreaks.clear();
|
|
|
|
return check(node) == ReachesEnd;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
// Sorted by how much code will be reachable from that state, i.e.
|
|
|
|
// ReachesEnd is guaranteed to reach more code than Break and so on.
|
|
|
|
enum State
|
|
|
|
{
|
|
|
|
ReachesEnd = 0,
|
|
|
|
Break = 1,
|
|
|
|
Continue = 2,
|
|
|
|
ReturnOrThrow = 3
|
|
|
|
};
|
|
|
|
State _state;
|
2011-09-16 10:35:48 +02:00
|
|
|
QHash<QString, Node *> _labels;
|
2011-09-07 07:21:38 +02:00
|
|
|
QSet<Node *> _labelledBreaks;
|
|
|
|
|
|
|
|
virtual void onUnreachable(Node *)
|
|
|
|
{}
|
|
|
|
|
|
|
|
virtual State check(Node *node)
|
|
|
|
{
|
|
|
|
_state = ReachesEnd;
|
|
|
|
Node::accept(node, this);
|
|
|
|
return _state;
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool preVisit(Node *ast) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
if (ast->expressionCast())
|
|
|
|
return false;
|
|
|
|
if (_state == ReachesEnd)
|
|
|
|
return true;
|
|
|
|
if (Statement *stmt = ast->statementCast())
|
|
|
|
onUnreachable(stmt);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(LabelledStatement *ast) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
// get the target statement
|
|
|
|
Statement *end = ast->statement;
|
|
|
|
forever {
|
|
|
|
if (LabelledStatement *label = cast<LabelledStatement *>(end))
|
|
|
|
end = label->statement;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!ast->label.isEmpty())
|
|
|
|
_labels[ast->label.toString()] = end;
|
2011-09-07 07:21:38 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(BreakStatement *ast) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
_state = Break;
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!ast->label.isEmpty()) {
|
2011-09-19 15:28:05 +02:00
|
|
|
if (Node *target = _labels.value(ast->label.toString())) {
|
2011-09-07 07:21:38 +02:00
|
|
|
_labelledBreaks.insert(target);
|
2011-09-19 15:28:05 +02:00
|
|
|
_state = ReturnOrThrow; // unwind until label is hit
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// labelled continues don't change control flow...
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(ContinueStatement *) override { _state = Continue; return false; }
|
2011-09-07 07:21:38 +02:00
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(ReturnStatement *) override { _state = ReturnOrThrow; return false; }
|
|
|
|
bool visit(ThrowStatement *) override { _state = ReturnOrThrow; return false; }
|
2011-09-07 07:21:38 +02:00
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(IfStatement *ast) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
State ok = check(ast->ok);
|
|
|
|
State ko = check(ast->ko);
|
|
|
|
_state = qMin(ok, ko);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleClause(StatementList *statements, State *result, bool *fallthrough)
|
|
|
|
{
|
|
|
|
State clauseResult = check(statements);
|
|
|
|
if (clauseResult == ReachesEnd) {
|
|
|
|
*fallthrough = true;
|
|
|
|
} else {
|
|
|
|
*fallthrough = false;
|
|
|
|
*result = qMin(*result, clauseResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(SwitchStatement *ast) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
if (!ast->block)
|
|
|
|
return false;
|
|
|
|
State result = ReturnOrThrow;
|
|
|
|
bool lastWasFallthrough = false;
|
|
|
|
|
|
|
|
for (CaseClauses *it = ast->block->clauses; it; it = it->next) {
|
|
|
|
if (it->clause)
|
|
|
|
handleClause(it->clause->statements, &result, &lastWasFallthrough);
|
|
|
|
}
|
|
|
|
if (ast->block->defaultClause)
|
|
|
|
handleClause(ast->block->defaultClause->statements, &result, &lastWasFallthrough);
|
|
|
|
for (CaseClauses *it = ast->block->moreClauses; it; it = it->next) {
|
|
|
|
if (it->clause)
|
|
|
|
handleClause(it->clause->statements, &result, &lastWasFallthrough);
|
|
|
|
}
|
|
|
|
|
2011-09-19 15:28:05 +02:00
|
|
|
if (lastWasFallthrough || !ast->block->defaultClause)
|
2011-09-07 07:21:38 +02:00
|
|
|
result = ReachesEnd;
|
|
|
|
if (result == Break || _labelledBreaks.contains(ast))
|
|
|
|
result = ReachesEnd;
|
|
|
|
_state = result;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(TryStatement *ast) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
State tryBody = check(ast->statement);
|
2011-09-16 15:32:47 +02:00
|
|
|
State catchBody = ReturnOrThrow;
|
|
|
|
if (ast->catchExpression)
|
|
|
|
catchBody = check(ast->catchExpression->statement);
|
|
|
|
State finallyBody = ReachesEnd;
|
|
|
|
if (ast->finallyExpression)
|
|
|
|
finallyBody = check(ast->finallyExpression->statement);
|
2011-09-07 07:21:38 +02:00
|
|
|
|
|
|
|
_state = qMax(qMin(tryBody, catchBody), finallyBody);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-19 15:28:05 +02:00
|
|
|
bool preconditionLoopStatement(Node *, Statement *body)
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
check(body);
|
2011-09-19 15:28:05 +02:00
|
|
|
_state = ReachesEnd; // condition could be false...
|
2011-09-07 07:21:38 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(WhileStatement *ast) override { return preconditionLoopStatement(ast, ast->statement); }
|
|
|
|
bool visit(ForStatement *ast) override { return preconditionLoopStatement(ast, ast->statement); }
|
|
|
|
bool visit(ForEachStatement *ast) override { return preconditionLoopStatement(ast, ast->statement); }
|
2011-09-19 15:28:05 +02:00
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
bool visit(DoWhileStatement *ast) override
|
2011-09-19 15:28:05 +02:00
|
|
|
{
|
|
|
|
check(ast->statement);
|
|
|
|
// not necessarily an infinite loop due to labelled breaks
|
|
|
|
if (_state == Continue)
|
|
|
|
_state = ReturnOrThrow;
|
|
|
|
if (_state == Break || _labelledBreaks.contains(ast))
|
|
|
|
_state = ReachesEnd;
|
|
|
|
return false;
|
|
|
|
}
|
2020-02-28 17:51:32 +01:00
|
|
|
|
|
|
|
void throwRecursionDepthError() override
|
|
|
|
{
|
|
|
|
// handle differently? ReturnOrThrow declares unreachable code, but probably leads to bogus warnings
|
|
|
|
_state = ReachesEnd;
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
class MarkUnreachableCode : protected ReachesEndCheck
|
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
QList<Message> _messages;
|
2017-07-28 12:33:37 +02:00
|
|
|
bool _emittedWarning = false;
|
2011-09-07 07:21:38 +02:00
|
|
|
|
|
|
|
public:
|
2011-09-28 15:16:00 +02:00
|
|
|
QList<Message> operator()(Node *ast)
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
_messages.clear();
|
|
|
|
check(ast);
|
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
2018-06-22 15:11:29 +02:00
|
|
|
State check(Node *node) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
bool oldwarning = _emittedWarning;
|
|
|
|
_emittedWarning = false;
|
|
|
|
State s = ReachesEndCheck::check(node);
|
|
|
|
_emittedWarning = oldwarning;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:11:29 +02:00
|
|
|
void onUnreachable(Node *node) override
|
2011-09-07 07:21:38 +02:00
|
|
|
{
|
|
|
|
if (_emittedWarning)
|
|
|
|
return;
|
|
|
|
_emittedWarning = true;
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
Message message(WarnUnreachable, SourceLocation());
|
2011-09-07 07:21:38 +02:00
|
|
|
if (Statement *statement = node->statementCast())
|
2011-09-28 15:16:00 +02:00
|
|
|
message.location = locationFromRange(statement->firstSourceLocation(), statement->lastSourceLocation());
|
2011-09-07 07:21:38 +02:00
|
|
|
else if (ExpressionNode *expr = node->expressionCast())
|
2011-09-28 15:16:00 +02:00
|
|
|
message.location = locationFromRange(expr->firstSourceLocation(), expr->lastSourceLocation());
|
|
|
|
if (message.isValid())
|
2011-09-07 07:21:38 +02:00
|
|
|
_messages += message;
|
|
|
|
}
|
2020-02-28 17:51:32 +01:00
|
|
|
|
|
|
|
void throwRecursionDepthError() override
|
|
|
|
{
|
|
|
|
_messages.append(Message(ErrHitMaximumRecursion, SourceLocation()));
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
};
|
|
|
|
|
2011-09-06 13:59:50 +02:00
|
|
|
class DeclarationsCheck : protected Visitor
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
|
|
|
public:
|
2011-09-28 15:16:00 +02:00
|
|
|
QList<Message> operator()(FunctionExpression *function)
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2010-12-06 10:03:53 +01:00
|
|
|
clear();
|
2010-11-25 13:38:15 +01:00
|
|
|
for (FormalParameterList *plist = function->formals; plist; plist = plist->next) {
|
2018-10-16 15:32:58 +02:00
|
|
|
if (!plist->element->bindingIdentifier.isEmpty())
|
|
|
|
_formalParameterNames += plist->element->bindingIdentifier.toString();
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Node::accept(function->body, this);
|
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
QList<Message> operator()(Node *node)
|
2010-12-06 10:03:53 +01:00
|
|
|
{
|
|
|
|
clear();
|
2011-09-07 07:21:38 +02:00
|
|
|
Node::accept(node, this);
|
2010-12-06 10:03:53 +01:00
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
protected:
|
2010-12-06 10:03:53 +01:00
|
|
|
void clear()
|
|
|
|
{
|
|
|
|
_messages.clear();
|
|
|
|
_declaredFunctions.clear();
|
|
|
|
_declaredVariables.clear();
|
|
|
|
_possiblyUndeclaredUses.clear();
|
|
|
|
_seenNonDeclarationStatement = false;
|
|
|
|
_formalParameterNames.clear();
|
2019-05-15 10:52:09 +02:00
|
|
|
QTC_ASSERT(_block == 0, _block = 0);
|
2010-12-06 10:03:53 +01:00
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
void postVisit(Node *ast) override
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
|
|
|
if (!_seenNonDeclarationStatement && ast->statementCast()
|
|
|
|
&& !cast<VariableStatement *>(ast)) {
|
|
|
|
_seenNonDeclarationStatement = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
bool visit(IdentifierExpression *ast) override
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-09-13 09:57:24 +02:00
|
|
|
if (ast->name.isEmpty())
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString &name = ast->name.toString();
|
2019-05-15 10:52:09 +02:00
|
|
|
if (!_declaredFunctions.contains(name)
|
|
|
|
&& !(_declaredVariables.contains(name)
|
|
|
|
|| _declaredBlockVariables.contains({name, _block}))) {
|
2010-11-25 13:38:15 +01:00
|
|
|
_possiblyUndeclaredUses[name].append(ast->identifierToken);
|
2019-05-15 10:52:09 +02:00
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
bool visit(VariableStatement *ast) override
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (_seenNonDeclarationStatement)
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->declarationKindToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
bool visit(PatternElement *ast) override
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2018-10-16 15:32:58 +02:00
|
|
|
if (ast->bindingIdentifier.isEmpty() || !ast->isVariableDeclaration())
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
2018-10-16 15:32:58 +02:00
|
|
|
const QString &name = ast->bindingIdentifier.toString();
|
2019-05-15 10:52:09 +02:00
|
|
|
VariableScope scope = ast->scope;
|
|
|
|
if (_formalParameterNames.contains(name)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
|
2019-05-15 10:52:09 +02:00
|
|
|
} else if (_declaredFunctions.contains(name)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnAlreadyFunction, ast->identifierToken, name);
|
2019-05-15 10:52:09 +02:00
|
|
|
} else if (scope == VariableScope::Let || scope == VariableScope::Const) {
|
|
|
|
if (_declaredBlockVariables.contains({name, _block}))
|
|
|
|
addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
|
|
|
|
} else if (scope == VariableScope::Var) {
|
|
|
|
if (_declaredVariables.contains(name)) {
|
|
|
|
addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
|
|
|
|
} else {
|
2019-09-04 08:59:07 +02:00
|
|
|
const auto found = std::find_if(_declaredBlockVariables.keyBegin(),
|
|
|
|
_declaredBlockVariables.keyEnd(),
|
|
|
|
[name](const auto &key) {
|
|
|
|
return key.first == name;
|
|
|
|
});
|
|
|
|
if (found != _declaredBlockVariables.keyEnd())
|
|
|
|
addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
|
2019-05-15 10:52:09 +02:00
|
|
|
}
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
if (_possiblyUndeclaredUses.contains(name)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
|
|
|
|
addMessage(WarnVarUsedBeforeDeclaration, loc, name);
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
_possiblyUndeclaredUses.remove(name);
|
|
|
|
}
|
2019-05-15 10:52:09 +02:00
|
|
|
if (scope == VariableScope::Let || scope == VariableScope::Const)
|
|
|
|
_declaredBlockVariables[{name, _block}] = ast;
|
|
|
|
else
|
|
|
|
_declaredVariables[name] = ast;
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
bool visit(FunctionDeclaration *ast) override
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (_seenNonDeclarationStatement)
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(HintDeclarationsShouldBeAtStartOfFunction, ast->functionToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
return visit(static_cast<FunctionExpression *>(ast));
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
bool visit(FunctionExpression *ast) override
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-09-13 09:57:24 +02:00
|
|
|
if (ast->name.isEmpty())
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
2011-09-13 09:57:24 +02:00
|
|
|
const QString &name = ast->name.toString();
|
2010-11-25 13:38:15 +01:00
|
|
|
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (_formalParameterNames.contains(name))
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnAlreadyFormalParameter, ast->identifierToken, name);
|
2019-05-15 10:52:09 +02:00
|
|
|
else if (_declaredVariables.contains(name) || _declaredBlockVariables.contains({name, _block}))
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnAlreadyVar, ast->identifierToken, name);
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
else if (_declaredFunctions.contains(name))
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnDuplicateDeclaration, ast->identifierToken, name);
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
if (FunctionDeclaration *decl = cast<FunctionDeclaration *>(ast)) {
|
|
|
|
if (_possiblyUndeclaredUses.contains(name)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
foreach (const SourceLocation &loc, _possiblyUndeclaredUses.value(name)) {
|
|
|
|
addMessage(WarnFunctionUsedBeforeDeclaration, loc, name);
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
_possiblyUndeclaredUses.remove(name);
|
|
|
|
}
|
|
|
|
_declaredFunctions[name] = decl;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-29 02:27:00 +02:00
|
|
|
bool openBlock()
|
2019-05-15 10:52:09 +02:00
|
|
|
{
|
|
|
|
++_block;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-07-29 02:27:00 +02:00
|
|
|
void closeBlock()
|
2019-05-15 10:52:09 +02:00
|
|
|
{
|
|
|
|
auto it = _declaredBlockVariables.begin();
|
|
|
|
auto end = _declaredBlockVariables.end();
|
|
|
|
while (it != end) {
|
|
|
|
if (it.key().second == _block)
|
|
|
|
it = _declaredBlockVariables.erase(it);
|
|
|
|
else
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
--_block;
|
|
|
|
}
|
|
|
|
|
2020-07-29 02:27:00 +02:00
|
|
|
bool visit(Block *) override
|
|
|
|
{
|
|
|
|
return openBlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void endVisit(Block *) override
|
|
|
|
{
|
|
|
|
closeBlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(Catch *) override
|
|
|
|
{
|
|
|
|
return openBlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void endVisit(Catch *) override
|
|
|
|
{
|
|
|
|
closeBlock();
|
|
|
|
}
|
|
|
|
|
2020-02-28 17:51:32 +01:00
|
|
|
void throwRecursionDepthError() override
|
|
|
|
{
|
|
|
|
addMessage(ErrHitMaximumRecursion, SourceLocation());
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
private:
|
2019-10-04 16:11:02 +02:00
|
|
|
void addMessage(StaticAnalysis::Type type, const SourceLocation &loc, const QString &arg1 = QString())
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
_messages.append(Message(type, loc, arg1));
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
QList<Message> _messages;
|
2010-11-25 13:38:15 +01:00
|
|
|
QStringList _formalParameterNames;
|
2018-10-16 15:32:58 +02:00
|
|
|
QHash<QString, PatternElement *> _declaredVariables;
|
2019-05-15 10:52:09 +02:00
|
|
|
QHash<QPair<QString, uint>, PatternElement *> _declaredBlockVariables;
|
2010-11-25 13:38:15 +01:00
|
|
|
QHash<QString, FunctionDeclaration *> _declaredFunctions;
|
|
|
|
QHash<QString, QList<SourceLocation> > _possiblyUndeclaredUses;
|
|
|
|
bool _seenNonDeclarationStatement;
|
2019-05-15 10:52:09 +02:00
|
|
|
uint _block = 0;
|
2010-11-25 13:38:15 +01:00
|
|
|
};
|
|
|
|
|
2016-10-26 14:15:02 +02:00
|
|
|
class IdsThatShouldNotBeUsedInDesigner : public QStringList
|
|
|
|
{
|
|
|
|
public:
|
2021-04-14 19:04:52 +02:00
|
|
|
IdsThatShouldNotBeUsedInDesigner()
|
|
|
|
: QStringList({"top", "bottom", "left", "right", "width", "height",
|
|
|
|
"x", "y", "opacity", "parent", "item", "flow",
|
|
|
|
"color", "margin", "padding", "print", "border", "font",
|
|
|
|
"text", "source", "state", "visible", "focus", "data",
|
2022-03-24 12:35:54 +01:00
|
|
|
"clip", "layer", "scale", "enabled", "anchors",
|
2022-04-26 09:20:50 +02:00
|
|
|
"texture", "shaderInfo", "sprite", "spriteSequence", "baseState"
|
|
|
|
"vector", "string", "url", "var", "point", "date", "size", "list",
|
|
|
|
"enumeration"})
|
2019-01-17 15:24:16 +01:00
|
|
|
{}
|
2016-10-26 14:15:02 +02:00
|
|
|
};
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
class VisualAspectsPropertyBlackList : public QStringList
|
|
|
|
{
|
|
|
|
public:
|
2019-01-17 15:24:16 +01:00
|
|
|
VisualAspectsPropertyBlackList() : QStringList({"x",
|
|
|
|
"y",
|
|
|
|
"z",
|
|
|
|
"width",
|
|
|
|
"height",
|
|
|
|
"color",
|
|
|
|
"opacity",
|
|
|
|
"scale",
|
|
|
|
"rotation",
|
|
|
|
"margins",
|
|
|
|
"verticalCenterOffset",
|
|
|
|
"horizontalCenterOffset",
|
|
|
|
"baselineOffset",
|
|
|
|
"bottomMargin",
|
|
|
|
"topMargin",
|
|
|
|
"leftMargin",
|
|
|
|
"rightMargin",
|
|
|
|
"baseline",
|
|
|
|
"centerIn",
|
|
|
|
"fill",
|
|
|
|
"left",
|
|
|
|
"right",
|
|
|
|
"mirrored",
|
|
|
|
"verticalCenter"
|
|
|
|
"horizontalCenter"})
|
|
|
|
{}
|
2012-09-17 14:06:03 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
class UnsupportedTypesByVisualDesigner : public QStringList
|
|
|
|
{
|
|
|
|
public:
|
2021-07-15 14:26:47 +02:00
|
|
|
UnsupportedTypesByVisualDesigner()
|
|
|
|
: QStringList({"Timer", "Package", "Particles", "ApplicationWindow"})
|
2019-01-17 15:24:16 +01:00
|
|
|
{}
|
2012-09-17 14:06:03 +02:00
|
|
|
};
|
2014-09-10 09:49:51 +02:00
|
|
|
|
2014-10-14 16:48:39 +02:00
|
|
|
class UnsupportedTypesByQmlUi : public QStringList
|
|
|
|
{
|
|
|
|
public:
|
2020-02-13 16:13:48 +01:00
|
|
|
UnsupportedTypesByQmlUi() : QStringList({"ShaderEffect",
|
2017-02-22 15:09:35 +01:00
|
|
|
"Drawer"})
|
2016-11-08 16:29:03 +01:00
|
|
|
{
|
|
|
|
append(UnsupportedTypesByVisualDesigner());
|
2014-10-14 16:48:39 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-09-10 09:49:51 +02:00
|
|
|
class UnsupportedRootObjectTypesByVisualDesigner : public QStringList
|
|
|
|
{
|
|
|
|
public:
|
2021-07-15 14:26:47 +02:00
|
|
|
UnsupportedRootObjectTypesByVisualDesigner()
|
|
|
|
: QStringList({"QtObject"
|
|
|
|
"ListModel"
|
|
|
|
"Component"
|
|
|
|
"Timer"
|
|
|
|
"Package",
|
|
|
|
"ApplicationWindow"})
|
2019-01-17 15:24:16 +01:00
|
|
|
{}
|
2014-09-10 09:49:51 +02:00
|
|
|
};
|
|
|
|
|
2014-10-14 16:48:39 +02:00
|
|
|
class UnsupportedRootObjectTypesByQmlUi : public QStringList
|
|
|
|
{
|
|
|
|
public:
|
2019-01-17 15:24:16 +01:00
|
|
|
UnsupportedRootObjectTypesByQmlUi() : QStringList({"Window",
|
|
|
|
"ApplicationWindow"})
|
|
|
|
{}
|
2014-10-14 16:48:39 +02:00
|
|
|
};
|
|
|
|
|
2010-02-23 17:02:50 +01:00
|
|
|
} // end of anonymous namespace
|
|
|
|
|
2016-10-26 14:15:02 +02:00
|
|
|
Q_GLOBAL_STATIC(IdsThatShouldNotBeUsedInDesigner, idsThatShouldNotBeUsedInDesigner)
|
2012-09-17 14:06:03 +02:00
|
|
|
Q_GLOBAL_STATIC(VisualAspectsPropertyBlackList, visualAspectsPropertyBlackList)
|
|
|
|
Q_GLOBAL_STATIC(UnsupportedTypesByVisualDesigner, unsupportedTypesByVisualDesigner)
|
2014-09-10 09:49:51 +02:00
|
|
|
Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByVisualDesigner, unsupportedRootObjectTypesByVisualDesigner)
|
2014-10-14 16:48:39 +02:00
|
|
|
Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByQmlUi, unsupportedRootObjectTypesByQmlUi)
|
|
|
|
Q_GLOBAL_STATIC(UnsupportedTypesByQmlUi, unsupportedTypesByQmlUi)
|
2012-09-17 14:06:03 +02:00
|
|
|
|
2011-07-13 15:04:27 +02:00
|
|
|
Check::Check(Document::Ptr doc, const ContextPtr &context)
|
2010-02-16 10:36:09 +01:00
|
|
|
: _doc(doc)
|
2011-07-13 15:04:27 +02:00
|
|
|
, _context(context)
|
|
|
|
, _scopeChain(doc, _context)
|
2011-07-12 14:55:27 +02:00
|
|
|
, _scopeBuilder(&_scopeChain)
|
2011-09-29 11:48:13 +02:00
|
|
|
, _importsOk(false)
|
2011-12-02 10:41:13 +01:00
|
|
|
, _inStatementBinding(false)
|
2019-01-17 15:24:16 +01:00
|
|
|
, _imports(nullptr)
|
2013-11-13 17:20:01 +01:00
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
2013-11-13 17:20:01 +01:00
|
|
|
_imports = context->imports(doc.data());
|
|
|
|
if (_imports && !_imports->importFailed()) {
|
2011-09-29 11:48:13 +02:00
|
|
|
_importsOk = true;
|
2013-11-13 17:20:01 +01:00
|
|
|
}
|
2011-09-29 11:48:13 +02:00
|
|
|
|
2019-07-03 18:34:30 +02:00
|
|
|
_enabledMessages = Utils::toSet(Message::allMessageTypes());
|
2011-09-28 15:16:00 +02:00
|
|
|
disableMessage(HintAnonymousFunctionSpacing);
|
|
|
|
disableMessage(HintDeclareVarsInOneLine);
|
|
|
|
disableMessage(HintDeclarationsShouldBeAtStartOfFunction);
|
2011-09-30 10:07:37 +02:00
|
|
|
disableMessage(HintBinaryOperatorSpacing);
|
2011-09-30 12:11:58 +02:00
|
|
|
disableMessage(HintOneStatementPerLine);
|
2011-10-04 09:49:30 +02:00
|
|
|
disableMessage(HintExtraParentheses);
|
2014-10-14 10:10:58 +02:00
|
|
|
|
2020-03-05 16:50:49 +01:00
|
|
|
disableQmlDesignerChecks();
|
|
|
|
if (!isQtQuick2Ui())
|
2014-10-14 10:10:58 +02:00
|
|
|
disableQmlDesignerUiFileChecks();
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Check::~Check()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
QList<Message> Check::operator()()
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
|
|
|
_messages.clear();
|
2011-10-20 09:45:29 +02:00
|
|
|
scanCommentsForAnnotations();
|
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
Node::accept(_doc->ast(), this);
|
2011-10-20 09:45:29 +02:00
|
|
|
warnAboutUnnecessarySuppressions();
|
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
return _messages;
|
|
|
|
}
|
|
|
|
|
2019-10-04 16:11:02 +02:00
|
|
|
void Check::enableMessage(StaticAnalysis::Type type)
|
2011-09-28 15:16:00 +02:00
|
|
|
{
|
|
|
|
_enabledMessages.insert(type);
|
|
|
|
}
|
|
|
|
|
2019-10-04 16:11:02 +02:00
|
|
|
void Check::disableMessage(StaticAnalysis::Type type)
|
2011-09-28 15:16:00 +02:00
|
|
|
{
|
|
|
|
_enabledMessages.remove(type);
|
|
|
|
}
|
|
|
|
|
2014-10-14 10:10:58 +02:00
|
|
|
void Check::enableQmlDesignerChecks()
|
|
|
|
{
|
2014-10-15 12:32:26 +02:00
|
|
|
enableMessage(WarnImperativeCodeNotEditableInVisualDesigner);
|
|
|
|
enableMessage(WarnUnsupportedTypeInVisualDesigner);
|
|
|
|
enableMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner);
|
|
|
|
enableMessage(ErrUnsupportedRootTypeInVisualDesigner);
|
2016-10-26 14:15:02 +02:00
|
|
|
enableMessage(ErrInvalidIdeInVisualDesigner);
|
2014-10-14 10:10:58 +02:00
|
|
|
//## triggers too often ## check.enableMessage(StaticAnalysis::WarnUndefinedValueForVisualDesigner);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::disableQmlDesignerChecks()
|
|
|
|
{
|
|
|
|
disableMessage(WarnImperativeCodeNotEditableInVisualDesigner);
|
|
|
|
disableMessage(WarnUnsupportedTypeInVisualDesigner);
|
|
|
|
disableMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner);
|
|
|
|
disableMessage(WarnUndefinedValueForVisualDesigner);
|
|
|
|
disableMessage(WarnStatesOnlyInRootItemForVisualDesigner);
|
|
|
|
disableMessage(ErrUnsupportedRootTypeInVisualDesigner);
|
2016-10-26 14:15:02 +02:00
|
|
|
disableMessage(ErrInvalidIdeInVisualDesigner);
|
2014-10-14 10:10:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Check::enableQmlDesignerUiFileChecks()
|
|
|
|
{
|
2014-10-14 16:48:39 +02:00
|
|
|
enableMessage(ErrUnsupportedRootTypeInQmlUi);
|
|
|
|
enableMessage(ErrUnsupportedTypeInQmlUi);
|
|
|
|
enableMessage(ErrFunctionsNotSupportedInQmlUi);
|
|
|
|
enableMessage(ErrBlocksNotSupportedInQmlUi);
|
|
|
|
enableMessage(ErrBehavioursNotSupportedInQmlUi);
|
2014-10-15 12:32:26 +02:00
|
|
|
enableMessage(ErrStatesOnlyInRootItemInQmlUi);
|
|
|
|
enableMessage(ErrReferenceToParentItemNotSupportedInQmlUi);
|
2021-05-04 14:24:25 +02:00
|
|
|
enableMessage(ErrDoNotMixTranslationFunctionsInQmlUi);
|
2014-10-14 10:10:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Check::disableQmlDesignerUiFileChecks()
|
|
|
|
{
|
2014-10-14 16:48:39 +02:00
|
|
|
disableMessage(ErrUnsupportedRootTypeInQmlUi);
|
|
|
|
disableMessage(ErrUnsupportedTypeInQmlUi);
|
|
|
|
disableMessage(ErrFunctionsNotSupportedInQmlUi);
|
|
|
|
disableMessage(ErrBlocksNotSupportedInQmlUi);
|
|
|
|
disableMessage(ErrBehavioursNotSupportedInQmlUi);
|
2014-10-15 12:32:26 +02:00
|
|
|
disableMessage(ErrStatesOnlyInRootItemInQmlUi);
|
|
|
|
disableMessage(ErrReferenceToParentItemNotSupportedInQmlUi);
|
2021-05-04 14:24:25 +02:00
|
|
|
disableMessage(ErrDoNotMixTranslationFunctionsInQmlUi);
|
2014-10-14 10:10:58 +02:00
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
bool Check::preVisit(Node *ast)
|
|
|
|
{
|
|
|
|
_chain.append(ast);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::postVisit(Node *)
|
|
|
|
{
|
|
|
|
_chain.removeLast();
|
|
|
|
}
|
|
|
|
|
2010-02-19 12:25:26 +01:00
|
|
|
bool Check::visit(UiProgram *)
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-09 13:13:58 +01:00
|
|
|
bool Check::visit(UiImport *ast)
|
|
|
|
{
|
|
|
|
ShortImportInfo info;
|
|
|
|
if (auto ver = ast->version)
|
|
|
|
info.second = LanguageUtils::ComponentVersion(ver->majorVersion, ver->minorVersion);
|
|
|
|
|
|
|
|
if (!ast->fileName.isNull()) // it must be a file import
|
|
|
|
info.first = ast->fileName.toString();
|
|
|
|
else // no file import - construct full uri
|
|
|
|
info.first = toString(ast->importUri);
|
|
|
|
|
|
|
|
if (m_importInfo.contains(info)) {
|
|
|
|
SourceLocation location = ast->firstSourceLocation();
|
|
|
|
location.length = ast->lastSourceLocation().end();
|
|
|
|
addMessage(WarnDuplicateImport, location, info.first);
|
|
|
|
}
|
|
|
|
m_importInfo.append(info);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
bool Check::visit(UiObjectInitializer *)
|
|
|
|
{
|
2012-09-17 14:06:03 +02:00
|
|
|
QString typeName;
|
2010-12-13 16:46:29 +01:00
|
|
|
m_propertyStack.push(StringSet());
|
2012-09-17 14:06:03 +02:00
|
|
|
UiQualifiedId *qualifiedTypeId = qualifiedTypeNameId(parent());
|
|
|
|
if (qualifiedTypeId) {
|
|
|
|
typeName = qualifiedTypeId->name.toString();
|
2021-09-28 15:23:42 +02:00
|
|
|
if (typeName == "Component") {
|
2012-09-17 14:06:03 +02:00
|
|
|
m_idStack.push(StringSet());
|
2021-09-28 15:23:42 +02:00
|
|
|
_componentChildCount = 0;
|
|
|
|
}
|
2012-09-17 14:06:03 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 11:11:43 +02:00
|
|
|
m_typeStack.push(typeName);
|
2012-09-17 14:06:03 +02:00
|
|
|
|
2010-12-13 16:46:29 +01:00
|
|
|
if (m_idStack.isEmpty())
|
|
|
|
m_idStack.push(StringSet());
|
2012-09-17 14:06:03 +02:00
|
|
|
|
2010-12-13 16:46:29 +01:00
|
|
|
return true;
|
2010-12-07 17:31:53 +01:00
|
|
|
}
|
|
|
|
|
2021-12-06 09:44:24 +01:00
|
|
|
bool Check::visit(AST::TemplateLiteral *ast)
|
|
|
|
{
|
|
|
|
Node::accept(ast->expression, this);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-09-28 15:23:42 +02:00
|
|
|
void Check::endVisit(UiObjectInitializer *uiObjectInitializer)
|
2010-12-07 17:31:53 +01:00
|
|
|
{
|
2021-11-22 12:28:41 +01:00
|
|
|
Q_UNUSED(uiObjectInitializer)
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
m_propertyStack.pop();
|
2021-09-28 15:23:42 +02:00
|
|
|
|
|
|
|
const QString type = m_typeStack.pop();
|
|
|
|
|
|
|
|
if (type == "Component" && _componentChildCount == 0) {
|
|
|
|
SourceLocation loc;
|
|
|
|
UiObjectDefinition *objectDefinition = cast<UiObjectDefinition *>(parent());
|
|
|
|
if (objectDefinition)
|
|
|
|
loc = objectDefinition->qualifiedTypeNameId->identifierToken;
|
|
|
|
UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
|
|
|
|
if (objectBinding)
|
|
|
|
loc = objectBinding->qualifiedTypeNameId->identifierToken;
|
|
|
|
addMessage(WarnComponentRequiresChildren, loc);
|
|
|
|
}
|
|
|
|
|
2019-03-07 15:13:26 +01:00
|
|
|
UiObjectDefinition *objectDefinition = cast<UiObjectDefinition *>(parent());
|
2020-09-16 15:08:57 +02:00
|
|
|
if (objectDefinition && objectDefinition->qualifiedTypeNameId->name == QLatin1String("Component"))
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.pop();
|
|
|
|
UiObjectBinding *objectBinding = cast<UiObjectBinding *>(parent());
|
2020-09-16 15:08:57 +02:00
|
|
|
if (objectBinding && objectBinding->qualifiedTypeNameId->name == QLatin1String("Component"))
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.pop();
|
2010-12-07 17:31:53 +01:00
|
|
|
}
|
|
|
|
|
2020-02-28 17:51:32 +01:00
|
|
|
void Check::throwRecursionDepthError()
|
|
|
|
{
|
|
|
|
addMessage(ErrHitMaximumRecursion, SourceLocation());
|
|
|
|
}
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
void Check::checkProperty(UiQualifiedId *qualifiedId)
|
|
|
|
{
|
2011-10-07 14:04:06 +02:00
|
|
|
const QString id = toString(qualifiedId);
|
2016-04-13 15:08:07 +02:00
|
|
|
|
|
|
|
if (id.isEmpty())
|
|
|
|
return;
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
if (id.at(0).isLower()) {
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (m_propertyStack.top().contains(id))
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrPropertiesCanOnlyHaveOneBinding, fullLocationForQualifiedId(qualifiedId));
|
2010-12-07 17:31:53 +01:00
|
|
|
m_propertyStack.top().insert(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
bool Check::visit(UiObjectDefinition *ast)
|
|
|
|
{
|
2010-02-16 13:28:43 +01:00
|
|
|
visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
|
2010-02-16 10:36:09 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(UiObjectBinding *ast)
|
|
|
|
{
|
|
|
|
checkScopeObjectMember(ast->qualifiedId);
|
2014-10-14 16:48:39 +02:00
|
|
|
if (!ast->hasOnToken) {
|
2010-12-13 15:08:32 +01:00
|
|
|
checkProperty(ast->qualifiedId);
|
2014-10-14 16:48:39 +02:00
|
|
|
} else {
|
|
|
|
addMessage(ErrBehavioursNotSupportedInQmlUi, locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
|
|
|
}
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-16 13:28:43 +01:00
|
|
|
visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
|
2010-02-16 11:53:21 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
static bool expressionAffectsVisualAspects(BinaryExpression *expression)
|
|
|
|
{
|
|
|
|
if (expression->op == QSOperator::Assign
|
|
|
|
|| expression->op == QSOperator::InplaceSub
|
|
|
|
|| expression->op == QSOperator::InplaceAdd
|
|
|
|
|| expression->op == QSOperator::InplaceDiv
|
|
|
|
|| expression->op == QSOperator::InplaceMul
|
|
|
|
|| expression->op == QSOperator::InplaceOr
|
|
|
|
|| expression->op == QSOperator::InplaceXor
|
|
|
|
|| expression->op == QSOperator::InplaceAnd) {
|
|
|
|
|
|
|
|
const ExpressionNode *lhsValue = expression->left;
|
|
|
|
|
|
|
|
if (const IdentifierExpression* identifierExpression = cast<const IdentifierExpression *>(lhsValue)) {
|
|
|
|
if (visualAspectsPropertyBlackList()->contains(identifierExpression->name.toString()))
|
|
|
|
return true;
|
|
|
|
} else if (const FieldMemberExpression* fieldMemberExpression = cast<const FieldMemberExpression *>(lhsValue)) {
|
|
|
|
if (visualAspectsPropertyBlackList()->contains(fieldMemberExpression->name.toString()))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static UiQualifiedId *getRightMostIdentifier(UiQualifiedId *typeId)
|
|
|
|
{
|
|
|
|
if (typeId->next)
|
|
|
|
return getRightMostIdentifier(typeId->next);
|
|
|
|
|
|
|
|
return typeId;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool checkTypeForDesignerSupport(UiQualifiedId *typeId)
|
|
|
|
{
|
|
|
|
return unsupportedTypesByVisualDesigner()->contains(getRightMostIdentifier(typeId)->name.toString());
|
|
|
|
}
|
|
|
|
|
2014-10-14 16:48:39 +02:00
|
|
|
static bool checkTypeForQmlUiSupport(UiQualifiedId *typeId)
|
|
|
|
{
|
|
|
|
return unsupportedTypesByQmlUi()->contains(getRightMostIdentifier(typeId)->name.toString());
|
|
|
|
}
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
static bool checkTopLevelBindingForParentReference(ExpressionStatement *expStmt, const QString &source)
|
|
|
|
{
|
|
|
|
if (!expStmt)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
SourceLocation location = locationFromRange(expStmt->firstSourceLocation(), expStmt->lastSourceLocation());
|
2019-09-04 08:59:07 +02:00
|
|
|
QString stmtSource = source.mid(int(location.begin()), int(location.length));
|
2012-09-17 14:06:03 +02:00
|
|
|
|
2020-06-18 19:04:58 +02:00
|
|
|
if (stmtSource.contains(QRegularExpression("(^|\\W)parent\\.")))
|
2012-09-17 14:06:03 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-02-16 13:28:43 +01:00
|
|
|
void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
|
|
|
|
UiObjectInitializer *initializer)
|
2010-02-16 11:53:21 +01:00
|
|
|
{
|
2018-02-27 13:13:51 +01:00
|
|
|
// TODO: currently Qbs checks are not working properly
|
|
|
|
if (_doc->language() == Dialect::QmlQbs)
|
|
|
|
return;
|
|
|
|
|
2011-06-23 10:25:43 +02:00
|
|
|
// Don't do type checks if it's a grouped property binding.
|
|
|
|
// For instance: anchors { ... }
|
|
|
|
if (_doc->bind()->isGroupedPropertyBinding(ast)) {
|
2010-02-17 09:29:13 +01:00
|
|
|
checkScopeObjectMember(typeId);
|
|
|
|
// ### don't give up!
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
const SourceLocation typeErrorLocation = fullLocationForQualifiedId(typeId);
|
|
|
|
|
2014-10-14 16:48:39 +02:00
|
|
|
const QString typeName = getRightMostIdentifier(typeId)->name.toString();
|
|
|
|
|
2019-01-17 15:24:16 +01:00
|
|
|
if (!m_typeStack.isEmpty() && m_typeStack.last() == "State"
|
2016-11-08 21:09:37 +03:00
|
|
|
&& typeId->name.toString() != "AnchorChanges"
|
|
|
|
&& typeId->name.toString() != "ParentChange"
|
2016-07-01 14:36:34 +02:00
|
|
|
&& typeId->name.toString() != "PropertyChanges"
|
|
|
|
&& typeId->name.toString() != "StateChangeScript")
|
2016-06-21 18:33:15 +02:00
|
|
|
addMessage(StateCannotHaveChildItem, typeErrorLocation, typeName);
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
if (checkTypeForDesignerSupport(typeId))
|
2014-10-14 16:48:39 +02:00
|
|
|
addMessage(WarnUnsupportedTypeInVisualDesigner, typeErrorLocation, typeName);
|
|
|
|
|
2021-05-06 08:54:36 +02:00
|
|
|
if (typeId->next == nullptr && QFileInfo(_doc->fileName()).baseName() == typeName)
|
2020-06-04 15:45:08 +02:00
|
|
|
addMessage(ErrTypeIsInstantiatedRecursively, typeErrorLocation, typeName);
|
|
|
|
|
2014-10-14 16:48:39 +02:00
|
|
|
if (checkTypeForQmlUiSupport(typeId))
|
|
|
|
addMessage(ErrUnsupportedTypeInQmlUi, typeErrorLocation, typeName);
|
2012-09-17 14:06:03 +02:00
|
|
|
|
2021-05-06 08:54:36 +02:00
|
|
|
if (m_typeStack.count() > 1 && typeName == "State") {
|
2012-09-17 14:06:03 +02:00
|
|
|
addMessage(WarnStatesOnlyInRootItemForVisualDesigner, typeErrorLocation);
|
2014-10-15 12:32:26 +02:00
|
|
|
addMessage(ErrStatesOnlyInRootItemInQmlUi, typeErrorLocation);
|
|
|
|
}
|
2012-09-17 14:06:03 +02:00
|
|
|
|
2014-09-10 09:49:51 +02:00
|
|
|
if (m_typeStack.isEmpty()
|
|
|
|
&& unsupportedRootObjectTypesByVisualDesigner()->contains(typeName))
|
|
|
|
addMessage(ErrUnsupportedRootTypeInVisualDesigner,
|
|
|
|
locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()), typeName);
|
|
|
|
|
2014-10-14 16:48:39 +02:00
|
|
|
if (m_typeStack.isEmpty()
|
|
|
|
&& unsupportedRootObjectTypesByQmlUi()->contains(typeName))
|
|
|
|
addMessage(ErrUnsupportedRootTypeInQmlUi,
|
|
|
|
locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()), typeName);
|
|
|
|
|
2021-09-28 15:23:42 +02:00
|
|
|
if (!m_typeStack.isEmpty() && m_typeStack.last() == "Component") {
|
|
|
|
_componentChildCount++;
|
|
|
|
if (_componentChildCount > 1)
|
|
|
|
addMessage(ErrToManyComponentChildren, typeErrorLocation);
|
|
|
|
}
|
|
|
|
|
2011-05-20 13:19:16 +02:00
|
|
|
bool typeError = false;
|
2011-09-29 11:48:13 +02:00
|
|
|
if (_importsOk) {
|
|
|
|
const ObjectValue *prototype = _context->lookupType(_doc.data(), typeId);
|
|
|
|
if (!prototype) {
|
2011-05-20 13:19:16 +02:00
|
|
|
typeError = true;
|
2011-09-29 11:48:13 +02:00
|
|
|
addMessage(ErrUnknownComponent, typeErrorLocation);
|
|
|
|
} else {
|
|
|
|
PrototypeIterator iter(prototype, _context);
|
|
|
|
QList<const ObjectValue *> prototypes = iter.all();
|
|
|
|
if (iter.error() != PrototypeIterator::NoError)
|
|
|
|
typeError = true;
|
|
|
|
const ObjectValue *lastPrototype = prototypes.last();
|
2013-11-13 17:20:01 +01:00
|
|
|
|
2011-09-29 11:48:13 +02:00
|
|
|
if (iter.error() == PrototypeIterator::ReferenceResolutionError) {
|
|
|
|
if (const QmlPrototypeReference *ref =
|
2011-10-10 10:55:37 +02:00
|
|
|
value_cast<QmlPrototypeReference>(lastPrototype->prototype())) {
|
2011-09-29 11:48:13 +02:00
|
|
|
addMessage(ErrCouldNotResolvePrototypeOf, typeErrorLocation,
|
|
|
|
toString(ref->qmlTypeName()), lastPrototype->className());
|
|
|
|
} else {
|
|
|
|
addMessage(ErrCouldNotResolvePrototype, typeErrorLocation,
|
|
|
|
lastPrototype->className());
|
|
|
|
}
|
|
|
|
} else if (iter.error() == PrototypeIterator::CycleError) {
|
|
|
|
addMessage(ErrPrototypeCycle, typeErrorLocation,
|
2011-09-28 15:16:00 +02:00
|
|
|
lastPrototype->className());
|
2011-05-20 13:19:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_scopeBuilder.push(ast);
|
|
|
|
|
|
|
|
if (typeError) {
|
2010-02-19 15:55:11 +01:00
|
|
|
// suppress subsequent errors about scope object lookup by clearing
|
|
|
|
// the scope object list
|
|
|
|
// ### todo: better way?
|
2011-07-12 14:55:27 +02:00
|
|
|
_scopeChain.setQmlScopeObjects(QList<const ObjectValue *>());
|
2010-02-16 13:28:43 +01:00
|
|
|
}
|
|
|
|
|
2010-02-16 11:53:21 +01:00
|
|
|
Node::accept(initializer, this);
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-19 15:55:11 +01:00
|
|
|
_scopeBuilder.pop();
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(UiScriptBinding *ast)
|
|
|
|
{
|
2010-02-23 14:55:38 +01:00
|
|
|
// special case for id property
|
2020-09-16 15:08:57 +02:00
|
|
|
if (ast->qualifiedId->name == QLatin1String("id") && !ast->qualifiedId->next) {
|
2010-02-23 14:55:38 +01:00
|
|
|
if (! ast->statement)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
|
|
|
|
ast->statement->lastSourceLocation());
|
|
|
|
|
|
|
|
ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
|
|
|
|
if (!expStmt) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrIdExpected, loc);
|
2010-02-23 14:55:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-03-04 16:40:47 +01:00
|
|
|
QString id;
|
|
|
|
if (IdentifierExpression *idExp = cast<IdentifierExpression *>(expStmt->expression)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
id = idExp->name.toString();
|
2010-03-04 16:40:47 +01:00
|
|
|
} else if (StringLiteral *strExp = cast<StringLiteral *>(expStmt->expression)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
id = strExp->value.toString();
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrInvalidId, loc);
|
2010-03-04 16:40:47 +01:00
|
|
|
} else {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrIdExpected, loc);
|
2010-02-23 14:55:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-17 15:24:16 +01:00
|
|
|
if (id.isEmpty() || (!id.at(0).isLower() && id.at(0) != '_')) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrInvalidId, loc);
|
2010-02-23 14:55:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
2010-12-07 17:31:53 +01:00
|
|
|
|
2016-10-26 14:15:02 +02:00
|
|
|
if (idsThatShouldNotBeUsedInDesigner->contains(id)) {
|
|
|
|
addMessage(ErrInvalidIdeInVisualDesigner, loc);
|
|
|
|
}
|
|
|
|
|
2010-12-13 16:46:29 +01:00
|
|
|
if (m_idStack.top().contains(id)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrDuplicateId, loc);
|
2010-12-07 17:31:53 +01:00
|
|
|
return false;
|
|
|
|
}
|
2010-12-13 16:46:29 +01:00
|
|
|
m_idStack.top().insert(id);
|
2010-02-23 14:55:38 +01:00
|
|
|
}
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
if (m_typeStack.count() == 1
|
|
|
|
&& visualAspectsPropertyBlackList()->contains(ast->qualifiedId->name.toString())
|
|
|
|
&& checkTopLevelBindingForParentReference(cast<ExpressionStatement *>(ast->statement), _doc->source())) {
|
|
|
|
addMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner,
|
|
|
|
locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
2014-10-15 12:32:26 +02:00
|
|
|
addMessage(ErrReferenceToParentItemNotSupportedInQmlUi,
|
|
|
|
locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
2012-09-17 14:06:03 +02:00
|
|
|
}
|
|
|
|
|
2010-12-07 17:31:53 +01:00
|
|
|
checkProperty(ast->qualifiedId);
|
|
|
|
|
2011-09-01 15:04:21 +02:00
|
|
|
if (!ast->statement)
|
|
|
|
return false;
|
|
|
|
|
2010-02-19 15:10:39 +01:00
|
|
|
const Value *lhsValue = checkScopeObjectMember(ast->qualifiedId);
|
|
|
|
if (lhsValue) {
|
2011-09-01 15:04:21 +02:00
|
|
|
Evaluate evaluator(&_scopeChain);
|
|
|
|
const Value *rhsValue = evaluator(ast->statement);
|
2010-02-19 15:10:39 +01:00
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
if (visualAspectsPropertyBlackList()->contains(ast->qualifiedId->name.toString()) &&
|
|
|
|
rhsValue->asUndefinedValue()) {
|
|
|
|
addMessage(WarnUndefinedValueForVisualDesigner,
|
|
|
|
locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
|
|
|
}
|
|
|
|
|
2011-09-01 15:04:21 +02:00
|
|
|
const SourceLocation loc = locationFromRange(ast->statement->firstSourceLocation(),
|
|
|
|
ast->statement->lastSourceLocation());
|
|
|
|
AssignmentCheck assignmentCheck;
|
2011-09-28 15:16:00 +02:00
|
|
|
Message message = assignmentCheck(_doc, loc, lhsValue, rhsValue, ast->statement);
|
|
|
|
if (message.isValid())
|
|
|
|
addMessage(message);
|
2010-02-19 15:10:39 +01:00
|
|
|
}
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
checkBindingRhs(ast->statement);
|
2010-12-06 10:03:53 +01:00
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
Node::accept(ast->qualifiedId, this);
|
|
|
|
_scopeBuilder.push(ast);
|
2011-12-02 10:41:13 +01:00
|
|
|
_inStatementBinding = true;
|
2011-09-07 07:21:38 +02:00
|
|
|
Node::accept(ast->statement, this);
|
2011-12-02 10:41:13 +01:00
|
|
|
_inStatementBinding = false;
|
2011-09-07 07:21:38 +02:00
|
|
|
_scopeBuilder.pop();
|
|
|
|
|
|
|
|
return false;
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(UiArrayBinding *ast)
|
|
|
|
{
|
|
|
|
checkScopeObjectMember(ast->qualifiedId);
|
2010-12-07 17:31:53 +01:00
|
|
|
checkProperty(ast->qualifiedId);
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-05 15:37:14 +02:00
|
|
|
bool Check::visit(UiPublicMember *ast)
|
|
|
|
{
|
2011-10-19 14:09:15 +02:00
|
|
|
if (ast->type == UiPublicMember::Property) {
|
2021-01-13 02:02:49 +01:00
|
|
|
const QStringView typeName = ast->memberType->name;
|
2022-01-19 10:34:12 +01:00
|
|
|
|
|
|
|
// Check alias properties don't reference root item
|
|
|
|
// Item {
|
|
|
|
// id: root
|
|
|
|
// property alias p1: root
|
|
|
|
// property alias p2: root.child
|
|
|
|
//
|
|
|
|
// Item { id: child }
|
|
|
|
// }
|
|
|
|
// - Show error for alias property p1
|
|
|
|
// - Show warning for alias property p2
|
|
|
|
|
|
|
|
// Check if type and id stack only contain one item as we are only looking for alias
|
|
|
|
// properties in the root item.
|
|
|
|
if (typeName == QLatin1String("alias") && ast->type == AST::UiPublicMember::Property
|
|
|
|
&& m_typeStack.count() == 1 && m_idStack.count() == 1 && m_idStack.top().count() == 1) {
|
|
|
|
|
|
|
|
const QString rootId = m_idStack.top().values().first();
|
|
|
|
if (!rootId.isEmpty()) {
|
|
|
|
if (ExpressionStatement *exp = cast<ExpressionStatement *>(ast->statement)) {
|
|
|
|
ExpressionNode *node = exp->expression;
|
|
|
|
|
|
|
|
// Check for case property alias p1: root
|
|
|
|
if (IdentifierExpression *idExp = cast<IdentifierExpression *>(node)) {
|
|
|
|
if (!idExp->name.isEmpty() && idExp->name.toString() == rootId)
|
|
|
|
addMessage(ErrAliasReferRoot, idExp->identifierToken);
|
|
|
|
|
|
|
|
// Check for case property alias p2: root.child
|
|
|
|
} else if (FieldMemberExpression *fmExp = cast<FieldMemberExpression *>(node)) {
|
|
|
|
if (IdentifierExpression *base = cast<IdentifierExpression *>(fmExp->base)) {
|
|
|
|
if (!base->name.isEmpty() && base->name.toString() == rootId)
|
|
|
|
addMessage(WarnAliasReferRootHierarchy, base->identifierToken);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-13 02:02:49 +01:00
|
|
|
// warn about dubious use of var/variant
|
|
|
|
if (typeName == QLatin1String("variant") || typeName == QLatin1String("var")) {
|
|
|
|
Evaluate evaluator(&_scopeChain);
|
|
|
|
const Value *init = evaluator(ast->statement);
|
|
|
|
QString preferredType;
|
|
|
|
if (init->asNumberValue())
|
|
|
|
preferredType = tr("'int' or 'real'");
|
|
|
|
else if (init->asStringValue())
|
|
|
|
preferredType = "'string'";
|
|
|
|
else if (init->asBooleanValue())
|
|
|
|
preferredType = "'bool'";
|
|
|
|
else if (init->asColorValue())
|
|
|
|
preferredType = "'color'";
|
|
|
|
else if (init == _context->valueOwner()->qmlPointObject())
|
|
|
|
preferredType = "'point'";
|
|
|
|
else if (init == _context->valueOwner()->qmlRectObject())
|
|
|
|
preferredType = "'rect'";
|
|
|
|
else if (init == _context->valueOwner()->qmlSizeObject())
|
|
|
|
preferredType = "'size'";
|
|
|
|
else if (init == _context->valueOwner()->qmlVector2DObject())
|
|
|
|
preferredType = "'vector2d'";
|
|
|
|
else if (init == _context->valueOwner()->qmlVector3DObject())
|
|
|
|
preferredType = "'vector3d'";
|
|
|
|
else if (init == _context->valueOwner()->qmlVector4DObject())
|
|
|
|
preferredType = "'vector4d'";
|
|
|
|
else if (init == _context->valueOwner()->qmlQuaternionObject())
|
|
|
|
preferredType = "'quaternion'";
|
|
|
|
else if (init == _context->valueOwner()->qmlMatrix4x4Object())
|
|
|
|
preferredType = "'matrix4x4'";
|
|
|
|
|
|
|
|
if (!preferredType.isEmpty())
|
|
|
|
addMessage(HintPreferNonVarPropertyType, ast->typeToken, preferredType);
|
2011-09-05 15:37:14 +02:00
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
|
2011-10-19 14:09:15 +02:00
|
|
|
checkBindingRhs(ast->statement);
|
2011-09-07 07:21:38 +02:00
|
|
|
|
2011-10-19 14:09:15 +02:00
|
|
|
_scopeBuilder.push(ast);
|
2011-12-02 10:41:13 +01:00
|
|
|
_inStatementBinding = true;
|
2011-10-19 14:09:15 +02:00
|
|
|
Node::accept(ast->statement, this);
|
2011-12-02 10:41:13 +01:00
|
|
|
_inStatementBinding = false;
|
2011-10-19 14:09:15 +02:00
|
|
|
Node::accept(ast->binding, this);
|
|
|
|
_scopeBuilder.pop();
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
|
|
|
|
return false;
|
2011-09-05 15:37:14 +02:00
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
bool Check::visit(IdentifierExpression *)
|
2010-11-24 15:12:11 +01:00
|
|
|
{
|
|
|
|
// currently disabled: too many false negatives
|
|
|
|
return true;
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
// _lastValue = 0;
|
|
|
|
// if (!ast->name.isEmpty()) {
|
|
|
|
// Evaluate evaluator(&_scopeChain);
|
|
|
|
// _lastValue = evaluator.reference(ast);
|
|
|
|
// if (!_lastValue)
|
|
|
|
// addMessage(ErrUnknownIdentifier, ast->identifierToken);
|
2011-10-10 10:55:37 +02:00
|
|
|
// if (const Reference *ref = value_cast<Reference>(_lastValue)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
// _lastValue = _context->lookupReference(ref);
|
|
|
|
// if (!_lastValue)
|
|
|
|
// error(ast->identifierToken, tr("could not resolve"));
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return false;
|
2010-11-24 15:12:11 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
bool Check::visit(FieldMemberExpression *)
|
2010-11-24 15:12:11 +01:00
|
|
|
{
|
|
|
|
// currently disabled: too many false negatives
|
|
|
|
return true;
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
// 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;
|
2010-11-24 15:12:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(FunctionDeclaration *ast)
|
|
|
|
{
|
|
|
|
return visit(static_cast<FunctionExpression *>(ast));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(FunctionExpression *ast)
|
|
|
|
{
|
2014-10-14 16:48:39 +02:00
|
|
|
SourceLocation locfunc = ast->functionToken;
|
|
|
|
SourceLocation loclparen = ast->lparenToken;
|
|
|
|
|
2011-09-30 09:50:08 +02:00
|
|
|
if (ast->name.isEmpty()) {
|
|
|
|
if (locfunc.isValid() && loclparen.isValid()
|
|
|
|
&& (locfunc.startLine != loclparen.startLine
|
|
|
|
|| locfunc.end() + 1 != loclparen.begin())) {
|
|
|
|
addMessage(HintAnonymousFunctionSpacing, locationFromRange(locfunc, loclparen));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-29 12:24:26 +01:00
|
|
|
const bool isDirectInConnectionsScope =
|
|
|
|
(!m_typeStack.isEmpty() && m_typeStack.last() == "Connections");
|
|
|
|
|
|
|
|
if (!isDirectInConnectionsScope)
|
|
|
|
addMessage(ErrFunctionsNotSupportedInQmlUi, locationFromRange(locfunc, loclparen));
|
2014-10-14 16:48:39 +02:00
|
|
|
|
2011-09-06 13:59:50 +02:00
|
|
|
DeclarationsCheck bodyCheck;
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessages(bodyCheck(ast));
|
|
|
|
|
|
|
|
MarkUnreachableCode unreachableCheck;
|
|
|
|
addMessages(unreachableCheck(ast->body));
|
2010-11-25 13:38:15 +01:00
|
|
|
|
2010-11-24 15:12:11 +01:00
|
|
|
Node::accept(ast->formals, this);
|
2011-12-02 10:41:13 +01:00
|
|
|
|
|
|
|
const bool wasInStatementBinding = _inStatementBinding;
|
|
|
|
_inStatementBinding = false;
|
2010-11-24 15:12:11 +01:00
|
|
|
_scopeBuilder.push(ast);
|
|
|
|
Node::accept(ast->body, this);
|
|
|
|
_scopeBuilder.pop();
|
2011-12-02 10:41:13 +01:00
|
|
|
_inStatementBinding = wasInStatementBinding;
|
|
|
|
|
2010-11-24 15:12:11 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-12 10:44:11 +02:00
|
|
|
static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs)
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-10-10 12:53:28 +02:00
|
|
|
if (lhs->asUnknownValue() || rhs->asUnknownValue())
|
|
|
|
return true; // may coerce or not
|
2011-09-12 10:44:11 +02:00
|
|
|
|
|
|
|
if (lhs->asStringValue() && rhs->asNumberValue())
|
|
|
|
return true; // coerces string to number
|
|
|
|
|
|
|
|
if (lhs->asObjectValue() && rhs->asNumberValue())
|
|
|
|
return true; // coerces object to primitive
|
|
|
|
|
|
|
|
if (lhs->asObjectValue() && rhs->asStringValue())
|
|
|
|
return true; // coerces object to primitive
|
|
|
|
|
2011-10-10 12:53:28 +02:00
|
|
|
if (lhs->asBooleanValue() && (!rhs->asBooleanValue()
|
2021-01-13 02:02:49 +01:00
|
|
|
&& !rhs->asUndefinedValue()
|
|
|
|
&& !rhs->asNullValue()))
|
2011-09-12 10:44:11 +02:00
|
|
|
return true; // coerces bool to number
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-01-29 23:20:31 +01:00
|
|
|
static bool equalIsAlwaysFalse(const Value *lhs, const Value *rhs)
|
|
|
|
{
|
|
|
|
if ((lhs->asNullValue() || lhs->asUndefinedValue())
|
|
|
|
&& (rhs->asNumberValue() || rhs->asBooleanValue() || rhs->asStringValue()))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-10-13 15:41:38 +02:00
|
|
|
static bool isIntegerValue(const Value *value)
|
|
|
|
{
|
|
|
|
if (value->asNumberValue() || value->asIntValue())
|
|
|
|
return true;
|
|
|
|
if (auto obj = value->asObjectValue())
|
2022-02-07 15:10:44 +01:00
|
|
|
return obj->className() == "Number" || obj->className() == "int";
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isStringValue(const Value *value)
|
|
|
|
{
|
|
|
|
if (value->asStringValue())
|
|
|
|
return true;
|
|
|
|
if (auto obj = value->asObjectValue())
|
2022-04-13 13:11:57 +02:00
|
|
|
return obj->className() == "QString" || obj->className() == "string" || obj->className() == "String";
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isBooleanValue(const Value *value)
|
|
|
|
{
|
|
|
|
if (value->asBooleanValue())
|
|
|
|
return true;
|
|
|
|
if (auto obj = value->asObjectValue())
|
|
|
|
return obj->className() == "boolean" || obj->className() == "Boolean";
|
2021-10-13 15:41:38 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-01-29 23:20:31 +01:00
|
|
|
static bool strictCompareConstant(const Value *lhs, const Value *rhs)
|
|
|
|
{
|
2021-11-05 14:49:51 +01:00
|
|
|
// attached properties and working at runtime cases may be undefined at evaluation time
|
|
|
|
if (lhs->asUndefinedValue() || rhs->asUndefinedValue())
|
|
|
|
return false;
|
2021-01-29 23:20:31 +01:00
|
|
|
if (lhs->asUnknownValue() || rhs->asUnknownValue())
|
|
|
|
return false;
|
2021-10-13 15:41:38 +02:00
|
|
|
if (lhs->asFunctionValue() || rhs->asFunctionValue()) // function evaluation not implemented
|
|
|
|
return false;
|
|
|
|
if (isIntegerValue(lhs) && isIntegerValue(rhs))
|
|
|
|
return false;
|
2022-02-07 15:10:44 +01:00
|
|
|
if (isStringValue(lhs) && isStringValue(rhs))
|
|
|
|
return false;
|
2022-04-13 13:11:57 +02:00
|
|
|
if (isBooleanValue(lhs) && isBooleanValue(rhs))
|
|
|
|
return false;
|
2021-01-29 23:20:31 +01:00
|
|
|
if (lhs->asBooleanValue() && !rhs->asBooleanValue())
|
|
|
|
return true;
|
2021-11-05 14:49:51 +01:00
|
|
|
if (lhs->asNumberValue() && !rhs->asNumberValue())
|
2021-01-29 23:20:31 +01:00
|
|
|
return true;
|
|
|
|
if (lhs->asStringValue() && !rhs->asStringValue())
|
|
|
|
return true;
|
2021-11-05 14:49:51 +01:00
|
|
|
if (lhs->asObjectValue() && (!rhs->asObjectValue() || !rhs->asNullValue()))
|
2021-01-29 23:20:31 +01:00
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
bool Check::visit(BinaryExpression *ast)
|
|
|
|
{
|
2011-09-30 10:07:37 +02:00
|
|
|
const QString source = _doc->source();
|
|
|
|
|
|
|
|
// check spacing
|
|
|
|
SourceLocation op = ast->operatorToken;
|
2019-09-04 08:59:07 +02:00
|
|
|
if ((op.begin() > 0 && !source.at(int(op.begin()) - 1).isSpace())
|
|
|
|
|| (int(op.end()) < source.size() && !source.at(int(op.end())).isSpace())) {
|
2011-09-30 10:07:37 +02:00
|
|
|
addMessage(HintBinaryOperatorSpacing, op);
|
|
|
|
}
|
|
|
|
|
2012-09-17 14:06:03 +02:00
|
|
|
SourceLocation expressionSourceLocation = locationFromRange(ast->firstSourceLocation(),
|
|
|
|
ast->lastSourceLocation());
|
|
|
|
if (expressionAffectsVisualAspects(ast))
|
|
|
|
addMessage(WarnImperativeCodeNotEditableInVisualDesigner, expressionSourceLocation);
|
|
|
|
|
2011-09-30 10:07:37 +02:00
|
|
|
// check ==, !=
|
2010-11-25 13:38:15 +01:00
|
|
|
if (ast->op == QSOperator::Equal || ast->op == QSOperator::NotEqual) {
|
2011-09-28 15:16:00 +02:00
|
|
|
Evaluate eval(&_scopeChain);
|
2011-09-30 10:07:37 +02:00
|
|
|
const Value *lhsValue = eval(ast->left);
|
|
|
|
const Value *rhsValue = eval(ast->right);
|
|
|
|
if (shouldAvoidNonStrictEqualityCheck(lhsValue, rhsValue)
|
|
|
|
|| shouldAvoidNonStrictEqualityCheck(rhsValue, lhsValue)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(MaybeWarnEqualityTypeCoercion, ast->operatorToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
2021-01-29 23:20:31 +01:00
|
|
|
if (equalIsAlwaysFalse(lhsValue, rhsValue)
|
|
|
|
|| equalIsAlwaysFalse(rhsValue, lhsValue))
|
|
|
|
addMessage(WarnLogicalValueDoesNotDependOnValues, ast->operatorToken);
|
|
|
|
}
|
|
|
|
if (ast->op == QSOperator::StrictEqual || ast->op == QSOperator::StrictNotEqual) {
|
|
|
|
Evaluate eval(&_scopeChain);
|
|
|
|
const Value *lhsValue = eval(ast->left);
|
|
|
|
const Value *rhsValue = eval(ast->right);
|
|
|
|
if (strictCompareConstant(lhsValue, rhsValue)
|
|
|
|
|| strictCompareConstant(rhsValue, lhsValue)) {
|
|
|
|
addMessage(WarnLogicalValueDoesNotDependOnValues, ast->operatorToken);
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
2011-09-30 10:39:00 +02:00
|
|
|
|
|
|
|
// check odd + ++ combinations
|
|
|
|
const QLatin1Char newline('\n');
|
|
|
|
if (ast->op == QSOperator::Add || ast->op == QSOperator::Sub) {
|
|
|
|
QChar match;
|
2019-10-04 16:11:02 +02:00
|
|
|
StaticAnalysis::Type msg;
|
2011-09-30 10:39:00 +02:00
|
|
|
if (ast->op == QSOperator::Add) {
|
2019-01-17 15:24:16 +01:00
|
|
|
match = '+';
|
2011-09-30 10:39:00 +02:00
|
|
|
msg = WarnConfusingPluses;
|
|
|
|
} else {
|
|
|
|
QTC_CHECK(ast->op == QSOperator::Sub);
|
2019-01-17 15:24:16 +01:00
|
|
|
match = '-';
|
2011-09-30 10:39:00 +02:00
|
|
|
msg = WarnConfusingMinuses;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (int(op.end()) + 1 < source.size()) {
|
2019-09-04 08:59:07 +02:00
|
|
|
const QChar next = source.at(int(op.end()));
|
|
|
|
if (next.isSpace() && next != newline && source.at(int(op.end()) + 1) == match)
|
|
|
|
addMessage(msg, SourceLocation((op.begin()), 3, op.startLine, op.startColumn));
|
2011-09-30 10:39:00 +02:00
|
|
|
}
|
|
|
|
if (op.begin() >= 2) {
|
2019-09-04 08:59:07 +02:00
|
|
|
const QChar prev = source.at(int(op.begin()) - 1);
|
|
|
|
if (prev.isSpace() && prev != newline && source.at(int(op.begin()) - 2) == match)
|
2011-09-30 10:39:00 +02:00
|
|
|
addMessage(msg, SourceLocation(op.begin() - 2, 3, op.startLine, op.startColumn - 2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(Block *ast)
|
|
|
|
{
|
2016-10-28 16:10:18 +02:00
|
|
|
|
|
|
|
bool isDirectInConnectionsScope =
|
2019-01-17 15:24:16 +01:00
|
|
|
(!m_typeStack.isEmpty() && m_typeStack.last() == "Connections");
|
2016-10-28 16:10:18 +02:00
|
|
|
|
|
|
|
if (!isDirectInConnectionsScope)
|
|
|
|
addMessage(ErrBlocksNotSupportedInQmlUi, locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
2014-10-14 16:48:39 +02:00
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
if (Node *p = parent()) {
|
2011-09-28 15:16:00 +02:00
|
|
|
if (!cast<UiScriptBinding *>(p)
|
2011-09-07 11:01:13 +02:00
|
|
|
&& !cast<UiPublicMember *>(p)
|
2010-11-25 13:38:15 +01:00
|
|
|
&& !cast<TryStatement *>(p)
|
|
|
|
&& !cast<Catch *>(p)
|
|
|
|
&& !cast<Finally *>(p)
|
|
|
|
&& !cast<ForStatement *>(p)
|
|
|
|
&& !cast<ForEachStatement *>(p)
|
|
|
|
&& !cast<DoWhileStatement *>(p)
|
|
|
|
&& !cast<WhileStatement *>(p)
|
|
|
|
&& !cast<IfStatement *>(p)
|
|
|
|
&& !cast<SwitchStatement *>(p)
|
2021-05-07 09:19:39 +02:00
|
|
|
&& !isCaseOrDefault(p)
|
|
|
|
&& !cast<WithStatement *>(p)
|
|
|
|
&& hasVarStatement(ast)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnBlock, ast->lbraceToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
2011-09-07 11:01:13 +02:00
|
|
|
if (!ast->statements
|
2011-10-28 10:17:44 +02:00
|
|
|
&& cast<UiPublicMember *>(p)
|
2011-10-13 14:19:50 +02:00
|
|
|
&& ast->lbraceToken.startLine == ast->rbraceToken.startLine) {
|
|
|
|
addMessage(WarnUnintentinalEmptyBlock, locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
2011-09-07 11:01:13 +02:00
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(WithStatement *ast)
|
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnWith, ast->withToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(VoidExpression *ast)
|
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnVoid, ast->voidToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(Expression *ast)
|
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
if (ast->left && ast->right) {
|
2010-12-06 09:54:10 +01:00
|
|
|
Node *p = parent();
|
2018-10-16 15:32:58 +02:00
|
|
|
if (!cast<ForStatement *>(p)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnComma, ast->commaToken);
|
2010-12-06 09:54:10 +01:00
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(ExpressionStatement *ast)
|
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
if (ast->expression) {
|
2010-11-25 13:38:15 +01:00
|
|
|
bool ok = cast<CallExpression *>(ast->expression)
|
|
|
|
|| cast<DeleteExpression *>(ast->expression)
|
|
|
|
|| cast<PreDecrementExpression *>(ast->expression)
|
|
|
|
|| cast<PreIncrementExpression *>(ast->expression)
|
|
|
|
|| cast<PostIncrementExpression *>(ast->expression)
|
2011-04-21 09:43:24 +02:00
|
|
|
|| cast<PostDecrementExpression *>(ast->expression)
|
2018-12-07 11:18:27 +01:00
|
|
|
|| cast<YieldExpression *>(ast->expression)
|
2011-04-21 09:43:24 +02:00
|
|
|
|| cast<FunctionExpression *>(ast->expression);
|
2010-11-25 13:38:15 +01:00
|
|
|
if (BinaryExpression *binary = cast<BinaryExpression *>(ast->expression)) {
|
|
|
|
switch (binary->op) {
|
|
|
|
case QSOperator::Assign:
|
|
|
|
case QSOperator::InplaceAdd:
|
|
|
|
case QSOperator::InplaceAnd:
|
|
|
|
case QSOperator::InplaceDiv:
|
|
|
|
case QSOperator::InplaceLeftShift:
|
|
|
|
case QSOperator::InplaceRightShift:
|
|
|
|
case QSOperator::InplaceMod:
|
|
|
|
case QSOperator::InplaceMul:
|
|
|
|
case QSOperator::InplaceOr:
|
|
|
|
case QSOperator::InplaceSub:
|
|
|
|
case QSOperator::InplaceURightShift:
|
|
|
|
case QSOperator::InplaceXor:
|
|
|
|
ok = true;
|
2019-09-04 08:59:07 +02:00
|
|
|
break;
|
2010-11-25 13:38:15 +01:00
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (!ok)
|
2011-12-02 10:41:13 +01:00
|
|
|
ok = _inStatementBinding;
|
2010-11-25 13:38:15 +01:00
|
|
|
|
|
|
|
if (!ok) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnConfusingExpressionStatement,
|
|
|
|
locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation()));
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(IfStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->expression)
|
|
|
|
checkAssignInCondition(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(ForStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->condition)
|
|
|
|
checkAssignInCondition(ast->condition);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(WhileStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->expression)
|
|
|
|
checkAssignInCondition(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(DoWhileStatement *ast)
|
|
|
|
{
|
|
|
|
if (ast->expression)
|
|
|
|
checkAssignInCondition(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-11-28 14:58:01 +01:00
|
|
|
bool Check::visit(CaseBlock *ast)
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-11-28 14:58:01 +01:00
|
|
|
QList< QPair<SourceLocation, StatementList *> > clauses;
|
|
|
|
for (CaseClauses *it = ast->clauses; it; it = it->next)
|
2017-04-26 13:18:26 +02:00
|
|
|
clauses += {it->clause->caseToken, it->clause->statements};
|
2011-11-28 14:58:01 +01:00
|
|
|
if (ast->defaultClause)
|
2017-04-26 13:18:26 +02:00
|
|
|
clauses += {ast->defaultClause->defaultToken, ast->defaultClause->statements};
|
2011-11-28 14:58:01 +01:00
|
|
|
for (CaseClauses *it = ast->moreClauses; it; it = it->next)
|
2017-04-26 13:18:26 +02:00
|
|
|
clauses += {it->clause->caseToken, it->clause->statements};
|
2011-11-28 14:58:01 +01:00
|
|
|
|
2011-11-29 10:19:04 +01:00
|
|
|
// check all but the last clause for fallthrough
|
|
|
|
for (int i = 0; i < clauses.size() - 1; ++i) {
|
|
|
|
const SourceLocation nextToken = clauses[i + 1].first;
|
2011-11-28 14:58:01 +01:00
|
|
|
checkCaseFallthrough(clauses[i].second, clauses[i].first, nextToken);
|
|
|
|
}
|
2010-11-25 13:38:15 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-06 12:59:30 +02:00
|
|
|
static QString functionName(ExpressionNode *ast, SourceLocation *location)
|
|
|
|
{
|
|
|
|
if (IdentifierExpression *id = cast<IdentifierExpression *>(ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!id->name.isEmpty()) {
|
2011-09-06 12:59:30 +02:00
|
|
|
*location = id->identifierToken;
|
2011-09-13 09:57:24 +02:00
|
|
|
return id->name.toString();
|
2011-09-06 12:59:30 +02:00
|
|
|
}
|
|
|
|
} else if (FieldMemberExpression *fme = cast<FieldMemberExpression *>(ast)) {
|
2011-09-13 09:57:24 +02:00
|
|
|
if (!fme->name.isEmpty()) {
|
2011-09-06 12:59:30 +02:00
|
|
|
*location = fme->identifierToken;
|
2011-09-13 09:57:24 +02:00
|
|
|
return fme->name.toString();
|
2011-09-06 12:59:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2016-11-17 11:03:06 +01:00
|
|
|
static QString functionNamespace(ExpressionNode *ast)
|
|
|
|
{
|
|
|
|
if (FieldMemberExpression *fme = cast<FieldMemberExpression *>(ast)) {
|
|
|
|
if (!fme->name.isEmpty()) {
|
|
|
|
SourceLocation location;
|
|
|
|
return functionName(fme->base, &location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2011-09-06 12:59:30 +02:00
|
|
|
void Check::checkNewExpression(ExpressionNode *ast)
|
|
|
|
{
|
|
|
|
SourceLocation location;
|
|
|
|
const QString name = functionName(ast, &location);
|
|
|
|
if (name.isEmpty())
|
|
|
|
return;
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (!name.at(0).isUpper())
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnNewWithLowercaseFunction, location);
|
2011-09-06 12:59:30 +02:00
|
|
|
}
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
void Check::checkBindingRhs(Statement *statement)
|
|
|
|
{
|
|
|
|
if (!statement)
|
|
|
|
return;
|
|
|
|
|
|
|
|
DeclarationsCheck bodyCheck;
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessages(bodyCheck(statement));
|
|
|
|
|
|
|
|
MarkUnreachableCode unreachableCheck;
|
|
|
|
addMessages(unreachableCheck(statement));
|
|
|
|
}
|
|
|
|
|
2011-10-04 09:49:30 +02:00
|
|
|
void Check::checkExtraParentheses(ExpressionNode *expression)
|
|
|
|
{
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (NestedExpression *nested = cast<NestedExpression *>(expression))
|
2011-10-04 09:49:30 +02:00
|
|
|
addMessage(HintExtraParentheses, nested->lparenToken);
|
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
void Check::addMessages(const QList<Message> &messages)
|
|
|
|
{
|
|
|
|
foreach (const Message &msg, messages)
|
|
|
|
addMessage(msg);
|
|
|
|
}
|
|
|
|
|
2011-10-19 14:27:40 +02:00
|
|
|
static bool hasOnlySpaces(const QString &s)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < s.size(); ++i)
|
|
|
|
if (!s.at(i).isSpace())
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
void Check::addMessage(const Message &message)
|
|
|
|
{
|
2011-10-19 14:27:40 +02:00
|
|
|
if (message.isValid() && _enabledMessages.contains(message.type)) {
|
2019-09-04 08:59:07 +02:00
|
|
|
if (m_disabledMessageTypesByLine.contains(int(message.location.startLine))) {
|
|
|
|
QList<MessageTypeAndSuppression> &disabledMessages
|
|
|
|
= m_disabledMessageTypesByLine[int(message.location.startLine)];
|
2011-10-20 09:45:29 +02:00
|
|
|
for (int i = 0; i < disabledMessages.size(); ++i) {
|
|
|
|
if (disabledMessages[i].type == message.type) {
|
|
|
|
disabledMessages[i].wasSuppressed = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_messages += message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-04 16:11:02 +02:00
|
|
|
void Check::addMessage(StaticAnalysis::Type type, const SourceLocation &location, const QString &arg1, const QString &arg2)
|
2011-10-20 09:45:29 +02:00
|
|
|
{
|
|
|
|
addMessage(Message(type, location, arg1, arg2));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Check::scanCommentsForAnnotations()
|
|
|
|
{
|
|
|
|
m_disabledMessageTypesByLine.clear();
|
2020-06-18 19:04:58 +02:00
|
|
|
const QRegularExpression disableCommentPattern = Message::suppressionPattern();
|
2011-10-28 15:56:56 +02:00
|
|
|
|
2011-10-20 09:45:29 +02:00
|
|
|
foreach (const SourceLocation &commentLoc, _doc->engine()->comments()) {
|
2019-09-04 08:59:07 +02:00
|
|
|
const QString &comment = _doc->source().mid(int(commentLoc.begin()), int(commentLoc.length));
|
2011-10-28 15:56:56 +02:00
|
|
|
|
|
|
|
// enable all checks annotation
|
2019-01-17 15:24:16 +01:00
|
|
|
if (comment.contains("@enable-all-checks"))
|
2019-07-03 18:34:30 +02:00
|
|
|
_enabledMessages = Utils::toSet(Message::allMessageTypes());
|
2011-10-28 15:56:56 +02:00
|
|
|
|
|
|
|
// find all disable annotations
|
2011-10-20 09:45:29 +02:00
|
|
|
int lastOffset = -1;
|
|
|
|
QList<MessageTypeAndSuppression> disabledMessageTypes;
|
|
|
|
forever {
|
2020-06-18 19:04:58 +02:00
|
|
|
const QRegularExpressionMatch match = disableCommentPattern.match(comment, lastOffset + 1);
|
|
|
|
if (!match.hasMatch())
|
2011-10-19 14:27:40 +02:00
|
|
|
break;
|
2020-06-18 19:04:58 +02:00
|
|
|
lastOffset = match.capturedStart();
|
2011-10-20 09:45:29 +02:00
|
|
|
MessageTypeAndSuppression entry;
|
2020-06-18 19:04:58 +02:00
|
|
|
entry.type = static_cast<StaticAnalysis::Type>(match.captured(1).toInt());
|
2011-10-20 09:45:29 +02:00
|
|
|
entry.wasSuppressed = false;
|
2019-09-04 08:59:07 +02:00
|
|
|
entry.suppressionSource = SourceLocation(commentLoc.offset + quint32(lastOffset),
|
2020-06-18 19:04:58 +02:00
|
|
|
quint32(match.capturedLength()),
|
2011-10-20 09:45:29 +02:00
|
|
|
commentLoc.startLine,
|
2019-09-04 08:59:07 +02:00
|
|
|
commentLoc.startColumn + quint32(lastOffset));
|
2011-10-20 09:45:29 +02:00
|
|
|
disabledMessageTypes += entry;
|
|
|
|
}
|
|
|
|
if (!disabledMessageTypes.isEmpty()) {
|
2019-09-04 08:59:07 +02:00
|
|
|
quint32 appliesToLine = commentLoc.startLine;
|
2011-10-19 14:27:40 +02:00
|
|
|
|
2011-10-20 09:45:29 +02:00
|
|
|
// if the comment is preceded by spaces only, it applies to the next line
|
2011-10-19 14:27:40 +02:00
|
|
|
// note: startColumn is 1-based and *after* the starting // or /*
|
2011-11-07 13:16:37 +01:00
|
|
|
if (commentLoc.startColumn >= 3) {
|
2019-09-04 08:59:07 +02:00
|
|
|
const QString &beforeComment = _doc->source().mid(int(commentLoc.begin()
|
|
|
|
- commentLoc.startColumn + 1),
|
|
|
|
int(commentLoc.startColumn) - 3);
|
2011-10-20 09:45:29 +02:00
|
|
|
if (hasOnlySpaces(beforeComment))
|
|
|
|
++appliesToLine;
|
2011-10-19 14:27:40 +02:00
|
|
|
}
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
m_disabledMessageTypesByLine[int(appliesToLine)] += disabledMessageTypes;
|
2011-10-19 14:27:40 +02:00
|
|
|
}
|
|
|
|
}
|
2011-09-28 15:16:00 +02:00
|
|
|
}
|
|
|
|
|
2011-10-20 09:45:29 +02:00
|
|
|
void Check::warnAboutUnnecessarySuppressions()
|
2011-09-28 15:16:00 +02:00
|
|
|
{
|
2019-07-24 13:43:54 +02:00
|
|
|
for (auto it = m_disabledMessageTypesByLine.cbegin(), end = m_disabledMessageTypesByLine.cend();
|
|
|
|
it != end; ++it) {
|
2011-10-20 09:45:29 +02:00
|
|
|
foreach (const MessageTypeAndSuppression &entry, it.value()) {
|
|
|
|
if (!entry.wasSuppressed)
|
|
|
|
addMessage(WarnUnnecessaryMessageSuppression, entry.suppressionSource);
|
|
|
|
}
|
|
|
|
}
|
2011-09-07 07:21:38 +02:00
|
|
|
}
|
|
|
|
|
2013-11-13 17:20:01 +01:00
|
|
|
bool Check::isQtQuick2() const
|
|
|
|
{
|
2014-10-13 16:46:30 +02:00
|
|
|
if (_doc->language() == Dialect::Qml) {
|
|
|
|
foreach (const Import &import, _imports->all()) {
|
2019-01-17 15:24:16 +01:00
|
|
|
if (import.info.name() == "QtQuick"
|
2014-10-13 16:46:30 +02:00
|
|
|
&& import.info.version().majorVersion() == 2)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2013-11-13 17:20:01 +01:00
|
|
|
}
|
2014-10-13 16:46:30 +02:00
|
|
|
return _doc->language() == Dialect::QmlQtQuick2 || _doc->language() == Dialect::QmlQtQuick2Ui;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::isQtQuick2Ui() const
|
|
|
|
{
|
|
|
|
return _doc->language() == Dialect::QmlQtQuick2Ui;
|
2013-11-13 17:20:01 +01:00
|
|
|
}
|
|
|
|
|
2021-05-07 09:19:39 +02:00
|
|
|
bool Check::isCaseOrDefault(Node *n)
|
|
|
|
{
|
|
|
|
if (!cast<StatementList *>(n))
|
|
|
|
return false;
|
|
|
|
if (Node *p = parent(1))
|
|
|
|
return p->kind == Node::Kind_CaseClause || p->kind == Node::Kind_DefaultClause;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::hasVarStatement(AST::Block *b) const
|
|
|
|
{
|
|
|
|
QTC_ASSERT(b, return false);
|
|
|
|
StatementList *s = b->statements;
|
|
|
|
while (s) {
|
|
|
|
if (auto var = cast<VariableStatement *>(s->statement)) {
|
|
|
|
VariableDeclarationList *declList = var->declarations;
|
|
|
|
while (declList) {
|
|
|
|
if (declList->declaration && declList->declaration->scope == VariableScope::Var)
|
|
|
|
return true;
|
|
|
|
declList = declList->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s = s->next;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-09-06 12:59:30 +02:00
|
|
|
bool Check::visit(NewExpression *ast)
|
|
|
|
{
|
|
|
|
checkNewExpression(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(NewMemberExpression *ast)
|
|
|
|
{
|
|
|
|
checkNewExpression(ast->base);
|
2011-09-30 11:11:01 +02:00
|
|
|
|
|
|
|
// check for Number, Boolean, etc constructor usage
|
|
|
|
if (IdentifierExpression *idExp = cast<IdentifierExpression *>(ast->base)) {
|
2020-09-16 15:08:57 +02:00
|
|
|
const QStringView name = idExp->name;
|
|
|
|
if (name == QLatin1String("Number")) {
|
2011-09-30 11:11:01 +02:00
|
|
|
addMessage(WarnNumberConstructor, idExp->identifierToken);
|
2020-09-16 15:08:57 +02:00
|
|
|
} else if (name == QLatin1String("Boolean")) {
|
2011-09-30 11:11:01 +02:00
|
|
|
addMessage(WarnBooleanConstructor, idExp->identifierToken);
|
2020-09-16 15:08:57 +02:00
|
|
|
} else if (name == QLatin1String("String")) {
|
2011-09-30 11:11:01 +02:00
|
|
|
addMessage(WarnStringConstructor, idExp->identifierToken);
|
2020-09-16 15:08:57 +02:00
|
|
|
} else if (name == QLatin1String("Object")) {
|
2011-09-30 11:11:01 +02:00
|
|
|
addMessage(WarnObjectConstructor, idExp->identifierToken);
|
2020-09-16 15:08:57 +02:00
|
|
|
} else if (name == QLatin1String("Array")) {
|
2011-09-30 11:11:01 +02:00
|
|
|
bool ok = false;
|
|
|
|
if (ast->arguments && ast->arguments->expression && !ast->arguments->next) {
|
|
|
|
Evaluate evaluate(&_scopeChain);
|
|
|
|
const Value *arg = evaluate(ast->arguments->expression);
|
2011-10-10 12:53:28 +02:00
|
|
|
if (arg->asNumberValue() || arg->asUnknownValue())
|
2011-09-30 11:11:01 +02:00
|
|
|
ok = true;
|
|
|
|
}
|
|
|
|
if (!ok)
|
|
|
|
addMessage(WarnArrayConstructor, idExp->identifierToken);
|
2020-09-16 15:08:57 +02:00
|
|
|
} else if (name == QLatin1String("Function")) {
|
2011-09-30 11:11:01 +02:00
|
|
|
addMessage(WarnFunctionConstructor, idExp->identifierToken);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-06 12:59:30 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(CallExpression *ast)
|
|
|
|
{
|
|
|
|
// check for capitalized function name being called
|
2011-09-28 15:16:00 +02:00
|
|
|
SourceLocation location;
|
|
|
|
const QString name = functionName(ast->base, &location);
|
2014-10-14 16:48:39 +02:00
|
|
|
|
2016-11-17 11:03:06 +01:00
|
|
|
const QString namespaceName = functionNamespace(ast->base);
|
|
|
|
|
2017-02-10 14:14:32 +01:00
|
|
|
// We have to allow the translation functions
|
2016-11-17 11:03:06 +01:00
|
|
|
|
2019-01-16 12:08:20 +01:00
|
|
|
static const QStringList translationFunctions = {"qsTr", "qsTrId", "qsTranslate",
|
|
|
|
"qsTrNoOp", "qsTrIdNoOp", "qsTranslateNoOp"};
|
2017-02-10 14:14:32 +01:00
|
|
|
|
2019-01-16 12:08:20 +01:00
|
|
|
static const QStringList whiteListedFunctions = {"toString", "toFixed", "toExponential", "toPrecision", "isFinite", "isNaN", "valueOf",
|
|
|
|
"toLowerCase", "toLocaleString", "toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase",
|
|
|
|
"substring" , "charAt", "charCodeAt", "concat", "endsWith", "includes", "indexOf", "lastIndexOf"};
|
|
|
|
|
|
|
|
static const QStringList colorFunctions = {"lighter", "darker", "rgba", "tint", "hsla", "hsva"};
|
|
|
|
|
|
|
|
static const QStringList qtFunction = {"point", "rect", "size", "vector2d", "vector3d", "vector4d", "quaternion" "matrix4x4", "formatDate",
|
2021-09-29 18:07:54 +02:00
|
|
|
"formatDateTime", "formatTime", "resolvedUrl"};
|
2019-01-16 12:08:20 +01:00
|
|
|
|
|
|
|
const bool whiteListedFunction = translationFunctions.contains(name) || whiteListedFunctions.contains(name) || colorFunctions.contains(name) || qtFunction.contains(name);
|
2016-11-17 11:03:06 +01:00
|
|
|
|
2017-02-10 14:14:32 +01:00
|
|
|
// We allow the Math. functions
|
2016-11-17 11:03:06 +01:00
|
|
|
const bool isMathFunction = namespaceName == "Math";
|
2019-01-16 12:08:20 +01:00
|
|
|
const bool isDateFunction = namespaceName == "Date";
|
2016-05-26 15:12:20 +02:00
|
|
|
// allow adding connections with the help of the qt quick designer ui
|
2016-05-30 17:38:10 +02:00
|
|
|
bool isDirectInConnectionsScope =
|
|
|
|
(!m_typeStack.isEmpty() && m_typeStack.last() == QLatin1String("Connections"));
|
2019-01-18 10:11:21 +02:00
|
|
|
if (!whiteListedFunction && !isMathFunction && !isDateFunction && !isDirectInConnectionsScope)
|
2014-10-14 15:30:15 +02:00
|
|
|
addMessage(ErrFunctionsNotSupportedInQmlUi, location);
|
|
|
|
|
2021-05-04 14:24:25 +02:00
|
|
|
if (translationFunctions.contains(name)) {
|
|
|
|
TranslationFunction translationFunction = noTranslationfunction;
|
|
|
|
if (name == "qsTr" || name == "qsTrNoOp")
|
|
|
|
translationFunction = qsTr;
|
|
|
|
else if (name == "qsTrId" || name == "qsTrIdNoOp")
|
|
|
|
translationFunction = qsTrId;
|
|
|
|
else if (name == "qsTranslate" || name == "qsTranslateNoOp")
|
|
|
|
translationFunction = qsTranslate;
|
|
|
|
|
|
|
|
if (lastTransLationfunction != noTranslationfunction
|
|
|
|
&& lastTransLationfunction != translationFunction)
|
|
|
|
addMessage(ErrDoNotMixTranslationFunctionsInQmlUi, location);
|
|
|
|
|
|
|
|
lastTransLationfunction = translationFunction;
|
|
|
|
}
|
|
|
|
|
2019-12-05 17:04:43 +01:00
|
|
|
static const QStringList globalFunctions = {"String", "Boolean", "Date", "Number", "Object", "Array", "Symbol", "Object", "Function", "RegExp",
|
|
|
|
"QT_TR_NOOP", "QT_TRANSLATE_NOOP", "QT_TRID_NOOP"};
|
2019-01-16 12:08:20 +01:00
|
|
|
|
|
|
|
if (!name.isEmpty() && name.at(0).isUpper() && !globalFunctions.contains(name)) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnExpectedNewWithUppercaseFunction, location);
|
2011-09-06 12:59:30 +02:00
|
|
|
}
|
2011-09-30 11:15:15 +02:00
|
|
|
if (cast<IdentifierExpression *>(ast->base) && name == QLatin1String("eval"))
|
|
|
|
addMessage(WarnEval, location);
|
2011-09-06 12:59:30 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-30 12:11:58 +02:00
|
|
|
bool Check::visit(StatementList *ast)
|
|
|
|
{
|
|
|
|
SourceLocation warnStart;
|
|
|
|
SourceLocation warnEnd;
|
|
|
|
unsigned currentLine = 0;
|
|
|
|
for (StatementList *it = ast; it; it = it->next) {
|
|
|
|
if (!it->statement)
|
|
|
|
continue;
|
|
|
|
const SourceLocation itLoc = it->statement->firstSourceLocation();
|
|
|
|
if (itLoc.startLine != currentLine) { // first statement on a line
|
|
|
|
if (warnStart.isValid())
|
|
|
|
addMessage(HintOneStatementPerLine, locationFromRange(warnStart, warnEnd));
|
|
|
|
warnStart = SourceLocation();
|
|
|
|
currentLine = itLoc.startLine;
|
|
|
|
} else { // other statements on the same line
|
|
|
|
if (!warnStart.isValid())
|
|
|
|
warnStart = itLoc;
|
|
|
|
warnEnd = it->statement->lastSourceLocation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (warnStart.isValid())
|
|
|
|
addMessage(HintOneStatementPerLine, locationFromRange(warnStart, warnEnd));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-10-04 09:49:30 +02:00
|
|
|
bool Check::visit(ReturnStatement *ast)
|
|
|
|
{
|
|
|
|
checkExtraParentheses(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(ThrowStatement *ast)
|
|
|
|
{
|
|
|
|
checkExtraParentheses(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(DeleteExpression *ast)
|
|
|
|
{
|
|
|
|
checkExtraParentheses(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Check::visit(TypeOfExpression *ast)
|
|
|
|
{
|
|
|
|
checkExtraParentheses(ast->expression);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-04-06 11:44:55 +02:00
|
|
|
/// When something is changed here, also change ReadingContext::lookupProperty in
|
|
|
|
/// texttomodelmerger.cpp
|
2013-10-07 13:34:40 +02:00
|
|
|
/// ### Maybe put this into the context as a helper function.
|
2010-02-19 15:10:39 +01:00
|
|
|
const Value *Check::checkScopeObjectMember(const UiQualifiedId *id)
|
2010-02-16 10:36:09 +01:00
|
|
|
{
|
2017-08-22 17:24:54 +02:00
|
|
|
|
2011-09-29 11:48:13 +02:00
|
|
|
if (!_importsOk)
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2011-09-29 11:48:13 +02:00
|
|
|
|
2011-07-12 14:55:27 +02:00
|
|
|
QList<const ObjectValue *> scopeObjects = _scopeChain.qmlScopeObjects();
|
2010-02-19 15:55:11 +01:00
|
|
|
if (scopeObjects.isEmpty())
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2017-08-22 17:24:54 +02:00
|
|
|
const auto getAttachedTypes = [this, &scopeObjects](const QString &propertyName) {
|
|
|
|
bool isAttachedProperty = false;
|
|
|
|
if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
|
|
|
|
isAttachedProperty = true;
|
|
|
|
if (const ObjectValue *qmlTypes = _scopeChain.qmlTypes())
|
|
|
|
scopeObjects += qmlTypes;
|
|
|
|
}
|
|
|
|
return isAttachedProperty;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2010-02-16 10:36:09 +01:00
|
|
|
if (! id)
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr; // ### error?
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
if (id->name.isEmpty()) // possible after error recovery
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-04-06 11:44:55 +02:00
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
QString propertyName = id->name.toString();
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2019-01-17 15:24:16 +01:00
|
|
|
if (propertyName == "id" && !id->next)
|
|
|
|
return nullptr; // ### should probably be a special value
|
2010-02-16 10:36:09 +01:00
|
|
|
|
|
|
|
// attached properties
|
2017-08-22 17:24:54 +02:00
|
|
|
bool isAttachedProperty = getAttachedTypes(propertyName);
|
2010-02-18 10:42:15 +01:00
|
|
|
|
2010-02-19 10:14:34 +01:00
|
|
|
if (scopeObjects.isEmpty())
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-16 10:36:09 +01:00
|
|
|
|
2010-02-18 14:21:53 +01:00
|
|
|
// global lookup for first part of id
|
2019-01-17 15:24:16 +01:00
|
|
|
const Value *value = nullptr;
|
2010-02-19 10:14:34 +01:00
|
|
|
for (int i = scopeObjects.size() - 1; i >= 0; --i) {
|
2011-07-13 15:04:27 +02:00
|
|
|
value = scopeObjects[i]->lookupMember(propertyName, _context);
|
2010-02-19 10:14:34 +01:00
|
|
|
if (value)
|
|
|
|
break;
|
|
|
|
}
|
2020-09-03 12:52:06 +02:00
|
|
|
|
|
|
|
const bool isListElementScope = (!m_typeStack.isEmpty() && m_typeStack.last() == "ListElement");
|
|
|
|
|
2020-09-11 07:00:30 +02:00
|
|
|
if (isListElementScope)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
if (!value) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrInvalidPropertyName, id->identifierToken, propertyName);
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
2010-02-18 15:01:26 +01:00
|
|
|
// can't look up members for attached properties
|
|
|
|
if (isAttachedProperty)
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-18 15:01:26 +01:00
|
|
|
|
2011-05-04 11:12:45 +02:00
|
|
|
// resolve references
|
|
|
|
if (const Reference *ref = value->asReference())
|
2011-07-13 15:04:27 +02:00
|
|
|
value = _context->lookupReference(ref);
|
2011-05-04 11:12:45 +02:00
|
|
|
|
2010-02-18 14:21:53 +01:00
|
|
|
// member lookup
|
|
|
|
const UiQualifiedId *idPart = id;
|
|
|
|
while (idPart->next) {
|
2011-10-10 10:55:37 +02:00
|
|
|
const ObjectValue *objectValue = value_cast<ObjectValue>(value);
|
2010-02-18 14:21:53 +01:00
|
|
|
if (! objectValue) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrDoesNotHaveMembers, idPart->identifierToken, propertyName);
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-18 14:21:53 +01:00
|
|
|
}
|
|
|
|
|
2011-09-13 09:57:24 +02:00
|
|
|
if (idPart->next->name.isEmpty()) {
|
2010-02-24 17:14:14 +01:00
|
|
|
// somebody typed "id." and error recovery still gave us a valid tree,
|
|
|
|
// so just bail out here.
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-24 17:14:14 +01:00
|
|
|
}
|
|
|
|
|
2010-02-18 14:21:53 +01:00
|
|
|
idPart = idPart->next;
|
2011-09-13 09:57:24 +02:00
|
|
|
propertyName = idPart->name.toString();
|
2017-08-22 17:24:54 +02:00
|
|
|
isAttachedProperty = getAttachedTypes(propertyName);
|
|
|
|
if (isAttachedProperty)
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-18 14:21:53 +01:00
|
|
|
|
2011-07-13 15:04:27 +02:00
|
|
|
value = objectValue->lookupMember(propertyName, _context);
|
2010-02-18 14:21:53 +01:00
|
|
|
if (! value) {
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(ErrInvalidMember, idPart->identifierToken, propertyName, objectValue->className());
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-02-18 14:21:53 +01:00
|
|
|
}
|
2017-08-09 17:16:06 +02:00
|
|
|
// resolve references
|
|
|
|
if (const Reference *ref = value->asReference())
|
|
|
|
value = _context->lookupReference(ref);
|
2010-02-18 14:21:53 +01:00
|
|
|
}
|
2010-02-19 15:10:39 +01:00
|
|
|
|
|
|
|
return value;
|
2010-02-16 10:36:09 +01:00
|
|
|
}
|
|
|
|
|
2010-11-25 13:38:15 +01:00
|
|
|
void Check::checkAssignInCondition(AST::ExpressionNode *condition)
|
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
if (BinaryExpression *binary = cast<BinaryExpression *>(condition)) {
|
|
|
|
if (binary->op == QSOperator::Assign)
|
|
|
|
addMessage(WarnAssignmentInCondition, binary->operatorToken);
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-28 14:58:01 +01:00
|
|
|
void Check::checkCaseFallthrough(StatementList *statements, SourceLocation errorLoc, SourceLocation nextLoc)
|
2010-11-25 13:38:15 +01:00
|
|
|
{
|
2011-09-28 15:16:00 +02:00
|
|
|
if (!statements)
|
2010-11-25 13:38:15 +01:00
|
|
|
return;
|
|
|
|
|
2011-09-07 07:21:38 +02:00
|
|
|
ReachesEndCheck check;
|
|
|
|
if (check(statements)) {
|
2011-11-28 14:58:01 +01:00
|
|
|
// check for fallthrough comments
|
|
|
|
if (nextLoc.isValid()) {
|
|
|
|
quint32 afterLastStatement = 0;
|
|
|
|
for (StatementList *it = statements; it; it = it->next) {
|
|
|
|
if (!it->next)
|
|
|
|
afterLastStatement = it->statement->lastSourceLocation().end();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (const SourceLocation &comment, _doc->engine()->comments()) {
|
|
|
|
if (comment.begin() < afterLastStatement
|
|
|
|
|| comment.end() > nextLoc.begin())
|
|
|
|
continue;
|
|
|
|
|
2019-09-04 08:59:07 +02:00
|
|
|
const QString &commentText = _doc->source().mid(int(comment.begin()),
|
|
|
|
int(comment.length));
|
2019-01-17 15:24:16 +01:00
|
|
|
if (commentText.contains("fall through")
|
|
|
|
|| commentText.contains("fall-through")
|
|
|
|
|| commentText.contains("fallthrough")) {
|
2011-11-28 14:58:01 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-28 15:16:00 +02:00
|
|
|
addMessage(WarnCaseWithoutFlowControl, errorLoc);
|
2010-11-25 13:38:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Node *Check::parent(int distance)
|
|
|
|
{
|
|
|
|
const int index = _chain.size() - 2 - distance;
|
|
|
|
if (index < 0)
|
2019-01-17 15:24:16 +01:00
|
|
|
return nullptr;
|
2010-11-25 13:38:15 +01:00
|
|
|
return _chain.at(index);
|
|
|
|
}
|