diff options
Diffstat (limited to 'tools/qmllint/findwarnings.cpp')
-rw-r--r-- | tools/qmllint/findwarnings.cpp | 591 |
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 ¶meter : 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; -} |