aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmllint/findwarnings.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qmllint/findwarnings.cpp')
-rw-r--r--tools/qmllint/findwarnings.cpp591
1 files changed, 0 insertions, 591 deletions
diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp
deleted file mode 100644
index e0e1c3831e..0000000000
--- a/tools/qmllint/findwarnings.cpp
+++ /dev/null
@@ -1,591 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the tools applications of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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
-** 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.
-**
-** 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.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "findwarnings.h"
-#include "checkidentifiers.h"
-
-#include <QtQmlCompiler/private/qqmljsscope_p.h>
-#include <QtQmlCompiler/private/qqmljstypedescriptionreader_p.h>
-#include <QtQmlCompiler/private/qqmljstypereader_p.h>
-
-#include <QtQml/private/qqmljsast_p.h>
-#include <QtQml/private/qqmljslexer_p.h>
-#include <QtQml/private/qqmljsparser_p.h>
-#include <QtQml/private/qv4codegen_p.h>
-#include <QtQml/private/qqmlimportresolver_p.h>
-
-#include <QtCore/qfile.h>
-#include <QtCore/qdiriterator.h>
-#include <QtCore/qscopedvaluerollback.h>
-
-void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope)
-{
- QList<QQmlJSScope::ConstPtr> scopes;
- while (!scope.isNull()) {
- if (scopes.contains(scope)) {
- QString inheritenceCycle;
- for (const auto &seen: qAsConst(scopes)) {
- if (!inheritenceCycle.isEmpty())
- inheritenceCycle.append(QLatin1String(" -> "));
- inheritenceCycle.append(seen->baseTypeName());
- }
-
- if (m_warnInheritanceCycle) {
- m_errors.append({
- QStringLiteral("%1 is part of an inheritance cycle: %2\n")
- .arg(scope->internalName())
- .arg(inheritenceCycle),
- QtWarningMsg,
- QQmlJS::SourceLocation()
- });
- }
-
- m_unknownImports.insert(scope->internalName());
- m_visitFailed = true;
- break;
- }
-
- scopes.append(scope);
-
- if (scope->baseTypeName().isEmpty()) {
- break;
- } else if (auto newScope = scope->baseType()) {
- scope = newScope;
- } else {
- m_errors.append({
- scope->baseTypeName() + QStringLiteral(
- " was not found. Did you add all import paths?\n"),
- QtWarningMsg,
- QQmlJS::SourceLocation()
- });
- m_unknownImports.insert(scope->baseTypeName());
- m_visitFailed = true;
- break;
- }
- }
-}
-
-void FindWarningVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope)
-{
- auto children = scope->childScopes();
- while (!children.isEmpty()) {
- auto childScope = children.takeFirst();
- const auto type = childScope->scopeType();
- switch (type) {
- case QQmlJSScope::GroupedPropertyScope:
- case QQmlJSScope::AttachedPropertyScope:
- if (!childScope->baseType()) {
- m_errors.append({
- QStringLiteral("unknown %1 property scope %2.")
- .arg(type == QQmlJSScope::GroupedPropertyScope
- ? QStringLiteral("grouped")
- : QStringLiteral("attached"),
- childScope->internalName()),
- QtWarningMsg,
- childScope->sourceLocation()
- });
- m_visitFailed = true;
- }
- children.append(childScope->childScopes());
- default:
- break;
- }
- }
-}
-
-void FindWarningVisitor::flushPendingSignalParameters()
-{
- const SignalHandler handler = m_signalHandlers[m_pendingSingalHandler];
- for (const QString &parameter : handler.signal.parameterNames()) {
- m_currentScope->insertJSIdentifier(
- parameter, {
- QQmlJSScope::JavaScriptIdentifier::Injected,
- m_pendingSingalHandler
- });
- }
- m_pendingSingalHandler = QQmlJS::SourceLocation();
-}
-
-void FindWarningVisitor::throwRecursionDepthError()
-{
- QQmlJSImportVisitor::throwRecursionDepthError();
- m_visitFailed = true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
-{
- if (m_pendingSingalHandler.isValid()) {
- enterEnvironment(QQmlJSScope::JSFunctionScope, "signalhandler", ast->firstSourceLocation());
- flushPendingSignalParameters();
- }
- return true;
-}
-
-void FindWarningVisitor::endVisit(QQmlJS::AST::ExpressionStatement *)
-{
- if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope
- && m_currentScope->baseTypeName() == "signalhandler") {
- leaveEnvironment();
- }
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::Block *block)
-{
- if (!QQmlJSImportVisitor::visit(block))
- return false;
- if (m_pendingSingalHandler.isValid())
- flushPendingSignalParameters();
- return true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::WithStatement *withStatement)
-{
- if (m_warnWithStatement) {
- m_errors.append({
- QStringLiteral(
- "with statements are strongly discouraged in QML "
- "and might cause false positives when analysing unqualified "
- "identifiers\n"),
- QtWarningMsg,
- withStatement->firstSourceLocation()
- });
- }
-
- return QQmlJSImportVisitor::visit(withStatement);
-}
-
-static QString signalName(QStringView handlerName)
-{
- if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
- QString signal = handlerName.mid(2).toString();
- for (int i = 0; i < signal.length(); ++i) {
- QChar &ch = signal[i];
- if (ch.isLower())
- return QString();
- if (ch.isUpper()) {
- ch = ch.toLower();
- return signal;
- }
- }
- }
- return QString();
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
-{
- using namespace QQmlJS::AST;
-
- const auto qmlScope = m_currentScope;
- if (!QQmlJSImportVisitor::visit(uisb))
- return false;
-
- auto name = uisb->qualifiedId->name;
- if (name == QLatin1String("id")) {
- // Figure out whether the current scope is the root scope.
- if (auto parentScope = qmlScope->parentScope()) {
- if (!parentScope->parentScope()) {
- const auto expstat = cast<ExpressionStatement *>(uisb->statement);
- const auto identexp = cast<IdentifierExpression *>(expstat->expression);
- m_rootId = identexp->name.toString();
- }
- }
- return true;
- }
-
- const QString signal = signalName(name);
- if (signal.isEmpty()) {
- for (const auto &childScope : qmlScope->childScopes()) {
- if ((childScope->scopeType() == QQmlJSScope::AttachedPropertyScope
- || childScope->scopeType() == QQmlJSScope::GroupedPropertyScope)
- && childScope->internalName() == name) {
- return true;
- }
- }
-
- if (!qmlScope->hasProperty(name.toString())) {
- m_errors.append({
- QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" "
- "exists in the current element.\n").arg(name),
- QtWarningMsg,
- uisb->firstSourceLocation()
- });
- m_visitFailed = true;
- return true;
- }
-
- const auto property = qmlScope->property(name.toString());
- if (!property.type()) {
- m_errors.append({
- QStringLiteral("No type found for property \"%1\". This may be due "
- "to a missing import statement or incomplete "
- "qmltypes files.\n").arg(name),
- QtWarningMsg,
- uisb->firstSourceLocation()
- });
- m_visitFailed = true;
-
- }
-
- return true;
- }
-
-
- if (!qmlScope->hasMethod(signal) && m_warnUnqualified) {
- m_errors.append({
- QStringLiteral("no matching signal found for handler \"%1\"\n")
- .arg(name.toString()),
- QtWarningMsg,
- uisb->firstSourceLocation()
- });
- m_visitFailed = true;
- return true;
- }
-
- QQmlJSMetaMethod scopeSignal;
- for (QQmlJSScope::ConstPtr scope = qmlScope; scope; scope = scope->baseType()) {
- const auto methods = scope->ownMethods();
- const auto methodsRange = methods.equal_range(signal);
- for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
- if (method->methodType() != QQmlJSMetaMethod::Signal)
- continue;
- scopeSignal = *method;
- break;
- }
- }
-
- const auto statement = uisb->statement;
- if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) {
- if (FunctionExpression *func = expr->expression->asFunctionDefinition()) {
- // functions are already handled
- // they do not get names inserted according to the signal, but access their formal
- // parameters. Let's still check if the names match, though.
- const QStringList signalParameters = scopeSignal.parameterNames();
- qsizetype i = 0, end = signalParameters.length();
- for (FormalParameterList *formal = func->formals;
- formal; ++i, formal = formal->next) {
- if (i == end) {
- m_errors.append({
- QStringLiteral("Signal handler for \"%2\" has more formal"
- " parameters than the signal it handles.")
- .arg(name),
- QtWarningMsg,
- uisb->firstSourceLocation()
- });
- m_visitFailed = true;
- }
-
- const QStringView handlerParameter = formal->element->bindingIdentifier;
- const qsizetype j = signalParameters.indexOf(handlerParameter);
- if (j == i || j < 0)
- continue;
-
- m_errors.append({
- QStringLiteral("Parameter %1 to signal handler for \"%2\""
- " is called \"%3\". The signal has a parameter"
- " of the same name in position %4.\n")
- .arg(i + 1).arg(name, handlerParameter).arg(j + 1),
- QtWarningMsg,
- uisb->firstSourceLocation()
- });
- m_visitFailed = true;
- }
-
- return true;
- }
- }
-
- const auto firstSourceLocation = statement->firstSourceLocation();
- bool hasMultilineStatementBody
- = statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
- m_pendingSingalHandler = firstSourceLocation;
- m_signalHandlers.insert(firstSourceLocation, {scopeSignal, hasMultilineStatementBody});
- return true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
-{
- m_memberAccessChains[m_currentScope].append(
- {{idexp->name.toString(), QString(), idexp->firstSourceLocation()}});
- m_fieldMemberBase = idexp;
- return true;
-}
-
-FindWarningVisitor::FindWarningVisitor(
- QQmlJSImporter *importer, QStringList qmltypesFiles, QString code, QString fileName,
- bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle)
- : QQmlJSImportVisitor(importer,
- implicitImportDirectory(fileName, importer->resourceFileMapper()),
- qmltypesFiles),
- m_code(std::move(code)),
- m_rootId(QLatin1String("<id>")),
- m_filePath(std::move(fileName)),
- m_colorOut(silent),
- m_warnUnqualified(warnUnqualified),
- m_warnWithStatement(warnWithStatement),
- m_warnInheritanceCycle(warnInheritanceCycle)
-{
- m_currentScope->setInternalName("global");
-
- // setup color output
- m_colorOut.insertMapping(Error, ColorOutput::RedForeground);
- m_colorOut.insertMapping(Warning, ColorOutput::PurpleForeground);
- m_colorOut.insertMapping(Info, ColorOutput::BlueForeground);
- m_colorOut.insertMapping(Normal, ColorOutput::DefaultColor);
- m_colorOut.insertMapping(Hint, ColorOutput::GreenForeground);
- QLatin1String jsGlobVars[] = {
- /* Not listed on the MDN page; browser and QML extensions: */
- // console/debug api
- QLatin1String("console"), QLatin1String("print"),
- // garbage collector
- QLatin1String("gc"),
- // i18n
- QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"),
- QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
- // XMLHttpRequest
- QLatin1String("XMLHttpRequest")
- };
-
- QQmlJSScope::JavaScriptIdentifier globalJavaScript = {
- QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- QQmlJS::SourceLocation()
- };
- for (const char **globalName = QV4::Compiler::Codegen::s_globalNames;
- *globalName != nullptr;
- ++globalName) {
- m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript);
- }
- for (const auto& jsGlobVar: jsGlobVars)
- m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript);
-}
-
-static MessageColors messageColor(QtMsgType type)
-{
- switch (type) {
- case QtDebugMsg:
- return Normal;
- case QtWarningMsg:
- return Warning;
- case QtCriticalMsg:
- case QtFatalMsg:
- return Error;
- case QtInfoMsg:
- return Info;
- }
-
- return Normal;
-}
-
-bool FindWarningVisitor::check()
-{
- for (const auto &error : qAsConst(m_errors)) {
- if (error.loc.isValid()) {
- m_colorOut.writePrefixedMessage(
- QStringLiteral("%1:%2: %3")
- .arg(error.loc.startLine).arg(error.loc.startColumn).arg(error.message),
- messageColor(error.type));
- } else {
- m_colorOut.writePrefixedMessage(error.message, messageColor(error.type));
- }
- }
-
- // now that all ids are known, revisit any Connections whose target were perviously unknown
- for (auto const &outstandingConnection: m_outstandingConnections) {
- auto targetScope = m_scopesById[outstandingConnection.targetName];
- if (outstandingConnection.scope) {
- for (const auto scope = targetScope; targetScope;
- targetScope = targetScope->baseType()) {
- const auto connectionMethods = targetScope->ownMethods();
- for (const auto &method : connectionMethods)
- outstandingConnection.scope->addOwnMethod(method);
- }
- }
- QScopedValueRollback<QQmlJSScope::Ptr> rollback(m_currentScope, outstandingConnection.scope);
- outstandingConnection.uiod->initializer->accept(this);
- }
-
- if (!m_warnUnqualified)
- return true;
-
- CheckIdentifiers check(&m_colorOut, m_code, m_rootScopeImports, m_filePath);
- return check(m_scopesById, m_signalHandlers, m_memberAccessChains, m_globalScope, m_rootId)
- && !m_visitFailed;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
-{
- if (!QQmlJSImportVisitor::visit(uiob))
- return false;
-
- checkInheritanceCycle(m_currentScope);
- return true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
-{
- using namespace QQmlJS::AST;
-
- if (!QQmlJSImportVisitor::visit(uiod))
- return false;
-
- const QString name = m_currentScope->baseTypeName();
- if (name.isEmpty() || name.front().isLower())
- return false; // Ignore grouped properties for now
-
- checkInheritanceCycle(m_currentScope);
-
- if (name.endsWith("Connections")) {
- QString target;
- auto member = uiod->initializer->members;
- while (member) {
- if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
- auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member);
- if (asBinding->qualifiedId->name == QLatin1String("target")) {
- if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
- auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression;
- if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) {
- target = idexpr->name.toString();
- } else {
- // more complex expressions are not supported
- }
- }
- break;
- }
- }
- member = member->next;
- }
- QQmlJSScope::ConstPtr targetScope;
- if (target.isEmpty()) {
- // no target set, connection comes from parentF
- QQmlJSScope::Ptr scope = m_currentScope;
- do {
- scope = scope->parentScope(); // TODO: rename method
- } while (scope->scopeType() != QQmlJSScope::QMLScope);
- targetScope = m_rootScopeImports.value(scope->baseTypeName());
- } else {
- // there was a target, check if we already can find it
- auto scopeIt = m_scopesById.find(target);
- if (scopeIt != m_scopesById.end()) {
- targetScope = *scopeIt;
- } else {
- m_outstandingConnections.push_back({target, m_currentScope, uiod});
- return false; // visit children later once target is known
- }
- }
- for (const auto scope = targetScope; targetScope; targetScope = targetScope->baseType()) {
- const auto connectionMethods = targetScope->ownMethods();
- for (const auto &method : connectionMethods)
- m_currentScope->addOwnMethod(method);
- }
- }
- return true;
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element)
-{
- if (element->isVariableDeclaration()) {
- QQmlJS::AST::BoundNames names;
- element->boundNames(&names);
- for (const auto &name : names) {
- m_currentScope->insertJSIdentifier(
- name.id, {
- (element->scope == QQmlJS::AST::VariableScope::Var)
- ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped
- : QQmlJSScope::JavaScriptIdentifier::LexicalScoped,
- element->firstSourceLocation()
- });
- }
- }
-
- return true;
-}
-
-void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod)
-{
- auto childScope = m_currentScope;
- QQmlJSImportVisitor::endVisit(uiod);
-
- if (m_warnUnqualified)
- checkGroupedAndAttachedScopes(childScope);
-
- if (m_currentScope == m_globalScope
- || m_currentScope->baseTypeName() == QStringLiteral("Component")) {
- return;
- }
-
- auto property = childScope->property(QStringLiteral("parent"));
- if (!property.propertyName().isEmpty()) {
- property.setType(QQmlJSScope::ConstPtr(m_currentScope));
- childScope->addOwnProperty(property);
- }
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::FieldMemberExpression *)
-{
- return true;
-}
-
-void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
-{
- using namespace QQmlJS::AST;
- ExpressionNode *base = fieldMember->base;
- while (auto *nested = cast<NestedExpression *>(base))
- base = nested->expression;
-
- if (m_fieldMemberBase == base) {
- QString type;
- if (auto *binary = cast<BinaryExpression *>(base)) {
- if (binary->op == QSOperator::As) {
- if (auto *right = cast<TypeExpression *>(binary->right))
- type = right->m_type->toString();
- }
- }
-
-
- auto &chain = m_memberAccessChains[m_currentScope];
- Q_ASSERT(!chain.last().isEmpty());
- chain.last().append(FieldMember {
- fieldMember->name.toString(), type, fieldMember->identifierToken
- });
- m_fieldMemberBase = fieldMember;
- } else {
- m_fieldMemberBase = nullptr;
- }
-}
-
-bool FindWarningVisitor::visit(QQmlJS::AST::BinaryExpression *)
-{
- return true;
-}
-
-void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
-{
- if (binExp->op == QSOperator::As && m_fieldMemberBase == binExp->left)
- m_fieldMemberBase = binExp;
- else
- m_fieldMemberBase = nullptr;
-}