From c38ea80c5dd71a20eade7b3a3b619c1996c6af0b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 30 Mar 2020 17:42:48 +0200 Subject: Move qmllint's metatype support to tools/shared We want to read qmltypes files and analyze scopes also from other tools. Furthermore, restructure the shared directory, so that each tool only includes what it needs. Change-Id: I96a2dcc8b1c5fac613592fb1867bf51fa5ef3a6e Reviewed-by: Simon Hausmann --- src/qmltyperegistrar/.prev_CMakeLists.txt | 1 - src/qmltyperegistrar/CMakeLists.txt | 1 - src/qmltyperegistrar/qmltyperegistrar.pro | 2 + tests/auto/qml/qmllint/tst_qmllint.cpp | 2 +- tools/qmlcachegen/CMakeLists.txt | 1 - tools/qmlcachegen/qmlcachegen.pro | 9 +- tools/qmlimportscanner/.prev_CMakeLists.txt | 1 - tools/qmlimportscanner/CMakeLists.txt | 1 - tools/qmlimportscanner/qmlimportscanner.pro | 8 +- tools/qmllint/.prev_CMakeLists.txt | 11 +- tools/qmllint/CMakeLists.txt | 11 +- tools/qmllint/checkidentifiers.cpp | 408 +++++++++++++++++ tools/qmllint/checkidentifiers.h | 57 +++ tools/qmllint/componentversion.cpp | 80 ---- tools/qmllint/componentversion.h | 66 --- tools/qmllint/findunqualified.cpp | 34 +- tools/qmllint/metatypes.h | 155 ------- tools/qmllint/qcoloroutput.h | 9 + tools/qmllint/qmllint.pro | 20 +- tools/qmllint/scopetree.cpp | 526 ---------------------- tools/qmllint/scopetree.h | 223 ---------- tools/qmllint/typedescriptionreader.cpp | 658 ---------------------------- tools/qmllint/typedescriptionreader.h | 95 ---- tools/qmlplugindump/qmlplugindump.pro | 10 +- tools/shared/componentversion.cpp | 80 ++++ tools/shared/componentversion.h | 66 +++ tools/shared/metatypes.h | 155 +++++++ tools/shared/scopetree.cpp | 172 ++++++++ tools/shared/scopetree.h | 215 +++++++++ tools/shared/shared.pri | 26 +- tools/shared/typedescriptionreader.cpp | 658 ++++++++++++++++++++++++++++ tools/shared/typedescriptionreader.h | 95 ++++ 32 files changed, 2004 insertions(+), 1852 deletions(-) create mode 100644 tools/qmllint/checkidentifiers.cpp create mode 100644 tools/qmllint/checkidentifiers.h delete mode 100644 tools/qmllint/componentversion.cpp delete mode 100644 tools/qmllint/componentversion.h delete mode 100644 tools/qmllint/metatypes.h delete mode 100644 tools/qmllint/scopetree.cpp delete mode 100644 tools/qmllint/scopetree.h delete mode 100644 tools/qmllint/typedescriptionreader.cpp delete mode 100644 tools/qmllint/typedescriptionreader.h create mode 100644 tools/shared/componentversion.cpp create mode 100644 tools/shared/componentversion.h create mode 100644 tools/shared/metatypes.h create mode 100644 tools/shared/scopetree.cpp create mode 100644 tools/shared/scopetree.h create mode 100644 tools/shared/typedescriptionreader.cpp create mode 100644 tools/shared/typedescriptionreader.h diff --git a/src/qmltyperegistrar/.prev_CMakeLists.txt b/src/qmltyperegistrar/.prev_CMakeLists.txt index 1d376d161f..c82a0e5a99 100644 --- a/src/qmltyperegistrar/.prev_CMakeLists.txt +++ b/src/qmltyperegistrar/.prev_CMakeLists.txt @@ -7,7 +7,6 @@ qt_add_tool(qmltyperegistrar SOURCES ../../tools/shared/qmlstreamwriter.cpp ../../tools/shared/qmlstreamwriter.h - ../../tools/shared/resourcefilemapper.cpp ../../tools/shared/resourcefilemapper.h qmltyperegistrar.cpp qmltypesclassdescription.cpp qmltypesclassdescription.h qmltypescreator.cpp qmltypescreator.h diff --git a/src/qmltyperegistrar/CMakeLists.txt b/src/qmltyperegistrar/CMakeLists.txt index be3c28dc22..487c31d613 100644 --- a/src/qmltyperegistrar/CMakeLists.txt +++ b/src/qmltyperegistrar/CMakeLists.txt @@ -8,7 +8,6 @@ qt_add_tool(qmltyperegistrar TOOLS_TARGET Qml # special case SOURCES ../../tools/shared/qmlstreamwriter.cpp ../../tools/shared/qmlstreamwriter.h - ../../tools/shared/resourcefilemapper.cpp ../../tools/shared/resourcefilemapper.h qmltyperegistrar.cpp qmltypesclassdescription.cpp qmltypesclassdescription.h qmltypescreator.cpp qmltypescreator.h diff --git a/src/qmltyperegistrar/qmltyperegistrar.pro b/src/qmltyperegistrar/qmltyperegistrar.pro index dff8f00ca3..7ed3986dd7 100644 --- a/src/qmltyperegistrar/qmltyperegistrar.pro +++ b/src/qmltyperegistrar/qmltyperegistrar.pro @@ -8,11 +8,13 @@ QMAKE_TARGET_DESCRIPTION = QML Types Registrar include(../../tools/shared/shared.pri) SOURCES += \ + $$QMLSTREAMWRITER_SOURCES \ qmltyperegistrar.cpp \ qmltypesclassdescription.cpp \ qmltypescreator.cpp HEADERS += \ + $$QMLSTREAMWRITER_HEADERS \ qmltypesclassdescription.h \ qmltypescreator.h diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 8697495a6f..04e2054e37 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -162,7 +162,7 @@ void TestQmllint::dirtyQmlCode_data() << QString(); QTest::newRow("incompleteQmltypes") << QStringLiteral("incompleteQmltypes.qml") - << QString("Warning: Type \"QPalette\" of member \"palette\" not found at 5:26") + << QString("Warning: Type \"QPalette\" of base \"palette\" not found when accessing member \"weDontKnowIt\" at 5:34") << QString(); QTest::newRow("inheritanceCylce") << QStringLiteral("Cycle1.qml") diff --git a/tools/qmlcachegen/CMakeLists.txt b/tools/qmlcachegen/CMakeLists.txt index 29302c1ef6..de842ea41b 100644 --- a/tools/qmlcachegen/CMakeLists.txt +++ b/tools/qmlcachegen/CMakeLists.txt @@ -7,7 +7,6 @@ qt_add_tool(qmlcachegen TOOLS_TARGET Qml # special case SOURCES - ../shared/qmlstreamwriter.cpp ../shared/qmlstreamwriter.h ../shared/resourcefilemapper.cpp ../shared/resourcefilemapper.h generateloader.cpp qmlcachegen.cpp diff --git a/tools/qmlcachegen/qmlcachegen.pro b/tools/qmlcachegen/qmlcachegen.pro index ec65cdb5e6..d6e4812e3f 100644 --- a/tools/qmlcachegen/qmlcachegen.pro +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -3,11 +3,16 @@ option(host_build) QT = qmldevtools-private DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII -SOURCES = qmlcachegen.cpp \ +include(../shared/shared.pri) + +SOURCES = \ + $$RESOURCEFILEMAPPER_SOURCES \ + qmlcachegen.cpp \ resourcefilter.cpp \ generateloader.cpp -include(../shared/shared.pri) +HEADERS = \ + $$RESOURCEFILEMAPPER_HEADERS TARGET = qmlcachegen diff --git a/tools/qmlimportscanner/.prev_CMakeLists.txt b/tools/qmlimportscanner/.prev_CMakeLists.txt index fbab757807..c3c05e2e0f 100644 --- a/tools/qmlimportscanner/.prev_CMakeLists.txt +++ b/tools/qmlimportscanner/.prev_CMakeLists.txt @@ -6,7 +6,6 @@ qt_add_tool(qmlimportscanner SOURCES - ../shared/qmlstreamwriter.cpp ../shared/qmlstreamwriter.h ../shared/resourcefilemapper.cpp ../shared/resourcefilemapper.h main.cpp DEFINES diff --git a/tools/qmlimportscanner/CMakeLists.txt b/tools/qmlimportscanner/CMakeLists.txt index 30a4babfcd..8047d43aa7 100644 --- a/tools/qmlimportscanner/CMakeLists.txt +++ b/tools/qmlimportscanner/CMakeLists.txt @@ -7,7 +7,6 @@ qt_add_tool(qmlimportscanner TOOLS_TARGET Qml # special case SOURCES - ../shared/qmlstreamwriter.cpp ../shared/qmlstreamwriter.h ../shared/resourcefilemapper.cpp ../shared/resourcefilemapper.h main.cpp DEFINES diff --git a/tools/qmlimportscanner/qmlimportscanner.pro b/tools/qmlimportscanner/qmlimportscanner.pro index 33089a5c48..9fd2a38956 100644 --- a/tools/qmlimportscanner/qmlimportscanner.pro +++ b/tools/qmlimportscanner/qmlimportscanner.pro @@ -3,9 +3,15 @@ option(host_build) QT = core qmldevtools-private DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII -SOURCES += main.cpp include(../shared/shared.pri) +SOURCES += \ + $$RESOURCEFILEMAPPER_SOURCES \ + main.cpp + +HEADERS += \ + $$RESOURCEFILEMAPPER_HEADERS + load(cmake_functions) CMAKE_BIN_DIR = $$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX]) diff --git a/tools/qmllint/.prev_CMakeLists.txt b/tools/qmllint/.prev_CMakeLists.txt index 9e3667fead..df661ebc57 100644 --- a/tools/qmllint/.prev_CMakeLists.txt +++ b/tools/qmllint/.prev_CMakeLists.txt @@ -6,14 +6,17 @@ qt_add_tool(qmllint SOURCES - componentversion.cpp componentversion.h + checkidentifiers.cpp checkidentifiers.h + ../shared/componentversion.cpp ../shared/componentversion.h findunqualified.cpp findunqualified.h importedmembersvisitor.cpp importedmembersvisitor.h main.cpp - metatypes.h + ../shared/metatypes.h qcoloroutput.cpp qcoloroutput.h - scopetree.cpp scopetree.h - typedescriptionreader.cpp typedescriptionreader.h + ../shared/scopetree.cpp ../shared/scopetree.h + ../shared/typedescriptionreader.cpp ../shared/typedescriptionreader.h + INCLUDE_DIRECTORIES + ../shared PUBLIC_LIBRARIES Qt::CorePrivate Qt::QmlDevToolsPrivate diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt index d4b0aad760..d294c2c78e 100644 --- a/tools/qmllint/CMakeLists.txt +++ b/tools/qmllint/CMakeLists.txt @@ -7,14 +7,17 @@ qt_add_tool(qmllint TOOLS_TARGET Qml # special case SOURCES - componentversion.cpp componentversion.h + checkidentifiers.cpp checkidentifiers.h + ../shared/componentversion.cpp ../shared/componentversion.h findunqualified.cpp findunqualified.h importedmembersvisitor.cpp importedmembersvisitor.h main.cpp - metatypes.h + ../shared/metatypes.h qcoloroutput.cpp qcoloroutput.h - scopetree.cpp scopetree.h - typedescriptionreader.cpp typedescriptionreader.h + ../shared/scopetree.cpp ../shared/scopetree.h + ../shared/typedescriptionreader.cpp ../shared/typedescriptionreader.h + INCLUDE_DIRECTORIES + ../shared PUBLIC_LIBRARIES Qt::CorePrivate Qt::QmlDevToolsPrivate diff --git a/tools/qmllint/checkidentifiers.cpp b/tools/qmllint/checkidentifiers.cpp new file mode 100644 index 0000000000..6b9e48ed38 --- /dev/null +++ b/tools/qmllint/checkidentifiers.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "checkidentifiers.h" +#include "qcoloroutput.h" + +#include +#include + +class IssueLocationWithContext +{ +public: + IssueLocationWithContext(const QString &code, const QQmlJS::SourceLocation &location) { + int before = std::max(0,code.lastIndexOf(QLatin1Char('\n'), location.offset)); + m_beforeText = code.midRef(before + 1, int(location.offset - (before + 1))); + m_issueText = code.midRef(location.offset, location.length); + int after = code.indexOf(QLatin1Char('\n'), int(location.offset + location.length)); + m_afterText = code.midRef(int(location.offset + location.length), + int(after - (location.offset+location.length))); + } + + QStringRef beforeText() const { return m_beforeText; } + QStringRef issueText() const { return m_issueText; } + QStringRef afterText() const { return m_afterText; } + +private: + QStringRef m_beforeText; + QStringRef m_issueText; + QStringRef m_afterText; +}; + +static void writeWarning(ColorOutput *out) +{ + out->write(QLatin1String("Warning: "), Warning); +} + +static const QStringList unknownBuiltins = { + // TODO: "string" should be added to builtins.qmltypes, and the special handling below removed + QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet + QStringLiteral("QRectF"), // TODO: should be added to builtins.qmltypes + QStringLiteral("QFont"), // TODO: should be added to builtins.qmltypes + QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values. + QStringLiteral("variant"), // Same for generic variants +}; + +void CheckIdentifiers::printContext(const QQmlJS::SourceLocation &location) const +{ + IssueLocationWithContext issueLocationWithContext {m_code, location}; + m_colorOut->write(issueLocationWithContext.beforeText().toString(), Normal); + m_colorOut->write(issueLocationWithContext.issueText().toString(), Error); + m_colorOut->write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'), Normal); + int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t')); + m_colorOut->write(QString::fromLatin1(" ").repeated( + issueLocationWithContext.beforeText().length() - tabCount) + + QString::fromLatin1("\t").repeated(tabCount) + + QString::fromLatin1("^").repeated(location.length) + + QLatin1Char('\n'), Normal); +} + +bool CheckIdentifiers::checkMemberAccess(const QVector &members, + const ScopeTree *scope) const +{ + QStringList expectedNext; + QString detectedRestrictiveName; + QString detectedRestrictiveKind; + + for (const ScopeTree::FieldMember &access : members) { + if (scope == nullptr) { + writeWarning(m_colorOut); + m_colorOut->write( + QString::fromLatin1("Type \"%1\" of base \"%2\" not found when accessing member \"%3\" at %4:%5.\n") + .arg(detectedRestrictiveKind) + .arg(detectedRestrictiveName) + .arg(access.m_name) + .arg(access.m_location.startLine) + .arg(access.m_location.startColumn), Normal); + printContext(access.m_location); + return false; + } + + const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name(); + + if (!detectedRestrictiveKind.isEmpty()) { + if (expectedNext.contains(access.m_name)) { + expectedNext.clear(); + continue; + } + + writeWarning(m_colorOut); + m_colorOut->write(QString::fromLatin1( + "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n") + .arg(detectedRestrictiveName) + .arg(detectedRestrictiveKind) + .arg(access.m_name) + .arg(access.m_location.startLine) + .arg(access.m_location.startColumn), Normal); + printContext(access.m_location); + return false; + } + + const auto properties = scope->properties(); + const auto scopeIt = properties.find(access.m_name); + if (scopeIt != properties.end()) { + const QString typeName = access.m_parentType.isEmpty() ? scopeIt->typeName() + : access.m_parentType; + if (scopeIt->isList()) { + detectedRestrictiveKind = QLatin1String("list"); + detectedRestrictiveName = access.m_name; + expectedNext.append(QLatin1String("length")); + continue; + } + + if (typeName == QLatin1String("string")) { + detectedRestrictiveKind = typeName; + detectedRestrictiveName = access.m_name; + expectedNext.append(QLatin1String("length")); + continue; + } + + if (const ScopeTree *type = scopeIt->type()) { + if (access.m_parentType.isEmpty()) { + scope = type; + continue; + } + } + + if (unknownBuiltins.contains(typeName)) + return true; + + const auto it = m_types.find(typeName); + if (it == m_types.end()) { + detectedRestrictiveKind = typeName; + detectedRestrictiveName = access.m_name; + scope = nullptr; + } else { + scope = it->get(); + } + continue; + } + + const auto methods = scope->methods(); + const auto scopeMethodIt = methods.find(access.m_name); + if (scopeMethodIt != methods.end()) + return true; // Access to property of JS function + + const auto enums= scope->enums(); + for (const auto enumerator : enums) { + for (const QString &key : enumerator.keys()) { + if (access.m_name == key) { + detectedRestrictiveKind = QLatin1String("enum"); + detectedRestrictiveName = access.m_name; + break; + } + } + if (!detectedRestrictiveName.isEmpty()) + break; + } + if (!detectedRestrictiveName.isEmpty()) + continue; + + auto type = m_types.value(scopeName); + bool typeFound = false; + while (type) { + const auto typeProperties = type->properties(); + const auto typeIt = typeProperties.find(access.m_name); + if (typeIt != typeProperties.end()) { + const ScopeTree *propType = access.m_parentType.isEmpty() + ? typeIt->type() + : m_types.value(access.m_parentType).get(); + scope = propType ? propType : m_types.value(typeIt->typeName()).get(); + typeFound = true; + break; + } + + const auto typeMethods = type->methods(); + const auto typeMethodIt = typeMethods.find(access.m_name); + if (typeMethodIt != typeMethods.end()) { + detectedRestrictiveName = access.m_name; + detectedRestrictiveKind = QLatin1String("method"); + typeFound = true; + break; + } + + type = m_types.value(type->superclassName()); + } + if (typeFound) + continue; + + if (access.m_name.front().isUpper() && scope->scopeType() == ScopeType::QMLScope) { + // may be an attached type + const auto it = m_types.find(access.m_name); + if (it != m_types.end() && !(*it)->attachedTypeName().isEmpty()) { + const auto attached = m_types.find((*it)->attachedTypeName()); + if (attached != m_types.end()) { + scope = attached->get(); + continue; + } + } + } + + writeWarning(m_colorOut); + m_colorOut->write(QString::fromLatin1( + "Property \"%1\" not found on type \"%2\" at %3:%4\n") + .arg(access.m_name) + .arg(scopeName) + .arg(access.m_location.startLine) + .arg(access.m_location.startColumn), Normal); + printContext(access.m_location); + return false; + } + + return true; +} + +bool CheckIdentifiers::operator()(const QHash &qmlIDs, + const ScopeTree *root, const QString &rootId) const +{ + bool noUnqualifiedIdentifier = true; + + // revisit all scopes + QQueue workQueue; + workQueue.enqueue(root); + while (!workQueue.empty()) { + const ScopeTree *currentScope = workQueue.dequeue(); + const auto unmatchedSignalHandlers = currentScope->unmatchedSignalHandlers(); + for (const auto &handler : unmatchedSignalHandlers) { + writeWarning(m_colorOut); + m_colorOut->write(QString::fromLatin1( + "no matching signal found for handler \"%1\" at %2:%3\n") + .arg(handler.first).arg(handler.second.startLine) + .arg(handler.second.startColumn), Normal); + printContext(handler.second); + } + + const auto memberAccessChains = currentScope->memberAccessChains(); + for (auto memberAccessChain : memberAccessChains) { + if (memberAccessChain.isEmpty()) + continue; + + const auto memberAccessBase = memberAccessChain.takeFirst(); + if (currentScope->isIdInCurrentJSScopes(memberAccessBase.m_name)) + continue; + + auto it = qmlIDs.find(memberAccessBase.m_name); + if (it != qmlIDs.end()) { + if (*it != nullptr) { + if (!checkMemberAccess(memberAccessChain, *it)) + noUnqualifiedIdentifier = false; + continue; + } else if (!memberAccessChain.isEmpty()) { + // It could be a qualified type name + const QString scopedName = memberAccessChain.first().m_name; + if (scopedName.front().isUpper()) { + const QString qualified = memberAccessBase.m_name + QLatin1Char('.') + + scopedName; + const auto typeIt = m_types.find(qualified); + if (typeIt != m_types.end()) { + memberAccessChain.takeFirst(); + if (!checkMemberAccess(memberAccessChain, typeIt->get())) + noUnqualifiedIdentifier = false; + continue; + } + } + } + } + + auto qmlScope = currentScope->currentQMLScope(); + if (qmlScope->methods().contains(memberAccessBase.m_name)) { + // a property of a JavaScript function + continue; + } + + const auto properties = qmlScope->properties(); + const auto qmlIt = properties.find(memberAccessBase.m_name); + if (qmlIt != properties.end()) { + if (memberAccessChain.isEmpty() || unknownBuiltins.contains(qmlIt->typeName())) + continue; + + if (!qmlIt->type()) { + writeWarning(m_colorOut); + m_colorOut->write(QString::fromLatin1( + "Type of property \"%2\" not found at %3:%4\n") + .arg(memberAccessBase.m_name) + .arg(memberAccessBase.m_location.startLine) + .arg(memberAccessBase.m_location.startColumn), Normal); + printContext(memberAccessBase.m_location); + noUnqualifiedIdentifier = false; + } else if (!checkMemberAccess(memberAccessChain, qmlIt->type())) { + noUnqualifiedIdentifier = false; + } + + continue; + } + + // TODO: Lots of builtins are missing + if (memberAccessBase.m_name == QLatin1String("Qt")) + continue; + + const auto typeIt = m_types.find(memberAccessBase.m_name); + if (typeIt != m_types.end()) { + if (!checkMemberAccess(memberAccessChain, typeIt->get())) + noUnqualifiedIdentifier = false; + continue; + } + + noUnqualifiedIdentifier = false; + writeWarning(m_colorOut); + const auto location = memberAccessBase.m_location; + m_colorOut->write(QString::fromLatin1("unqualified access at %1:%2\n") + .arg(location.startLine).arg(location.startColumn), + Normal); + + printContext(location); + + // root(JS) --> program(qml) --> (first element) + const auto firstElement = root->childScopes()[0]->childScopes()[0]; + if (firstElement->properties().contains(memberAccessBase.m_name) + || firstElement->methods().contains(memberAccessBase.m_name) + || firstElement->enums().contains(memberAccessBase.m_name)) { + m_colorOut->write(QLatin1String("Note: "), Info); + m_colorOut->write(memberAccessBase.m_name + QLatin1String(" is a meber of the root element\n"), Normal ); + m_colorOut->write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); + if (rootId == QLatin1String("")) { + m_colorOut->write(QLatin1String("Note: "), Warning); + m_colorOut->write(QLatin1String("You first have to give the root element an id\n")); + } + IssueLocationWithContext issueLocationWithContext {m_code, location}; + m_colorOut->write(issueLocationWithContext.beforeText().toString(), Normal); + m_colorOut->write(rootId + QLatin1Char('.'), Hint); + m_colorOut->write(issueLocationWithContext.issueText().toString(), Normal); + m_colorOut->write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal); + } else if (currentScope->isIdInjectedFromSignal(memberAccessBase.m_name)) { + auto methodUsages = currentScope->currentQMLScope()->injectedSignalIdentifiers() + .values(memberAccessBase.m_name); + auto location = memberAccessBase.m_location; + // sort the list of signal handlers by their occurrence in the source code + // then, we select the first one whose location is after the unqualified id + // and go one step backwards to get the one which we actually need + std::sort(methodUsages.begin(), methodUsages.end(), + [](const MethodUsage &m1, const MethodUsage &m2) { + return m1.loc.startLine < m2.loc.startLine + || (m1.loc.startLine == m2.loc.startLine + && m1.loc.startColumn < m2.loc.startColumn); + }); + auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(), + [&location](const MethodUsage &methodUsage) { + return location.startLine < methodUsage.loc.startLine + || (location.startLine == methodUsage.loc.startLine + && location.startColumn < methodUsage.loc.startColumn); + }); + auto methodUsage = *(--oneBehindIt); + m_colorOut->write(QLatin1String("Note: "), Info); + m_colorOut->write( + memberAccessBase.m_name + QString::fromLatin1( + " is accessible in this scope because " + "you are handling a signal at %1:%2\n") + .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn), + Normal); + m_colorOut->write(QLatin1String("Consider using a function instead\n"), Normal); + IssueLocationWithContext context {m_code, methodUsage.loc}; + m_colorOut->write(context.beforeText() + QLatin1Char(' ')); + m_colorOut->write(QLatin1String(methodUsage.hasMultilineHandlerBody + ? "function(" + : "("), + Hint); + const auto parameters = methodUsage.method.parameterNames(); + for (int numParams = parameters.size(); numParams > 0; --numParams) { + m_colorOut->write(parameters.at(parameters.size() - numParams), Hint); + if (numParams > 1) + m_colorOut->write(QLatin1String(", "), Hint); + } + m_colorOut->write(QLatin1String(methodUsage.hasMultilineHandlerBody ? ")" : ") => "), + Hint); + m_colorOut->write(QLatin1String(" {..."), Normal); + } + m_colorOut->write(QLatin1String("\n\n\n"), Normal); + } + const auto childScopes = currentScope->childScopes(); + for (auto const &childScope : childScopes) + workQueue.enqueue(childScope.get()); + } + return noUnqualifiedIdentifier; +} diff --git a/tools/qmllint/checkidentifiers.h b/tools/qmllint/checkidentifiers.h new file mode 100644 index 0000000000..ae924c491c --- /dev/null +++ b/tools/qmllint/checkidentifiers.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ + +#ifndef CHECKIDENTIFIERS_H +#define CHECKIDENTIFIERS_H + +#include "scopetree.h" + +class ColorOutput; + +class CheckIdentifiers +{ +public: + CheckIdentifiers(ColorOutput *colorOut, const QString &code, const QHash &types) : + m_colorOut(colorOut), m_code(code), m_types(types) + {} + + bool operator ()(const QHash &qmlIDs, + const ScopeTree *root, const QString &rootId) const; + +private: + bool checkMemberAccess(const QVector &members, + const ScopeTree *scope) const; + void printContext(const QQmlJS::SourceLocation &location) const; + + ColorOutput *m_colorOut = nullptr; + QString m_code; + QHash m_types; +}; + +#endif // CHECKIDENTIFIERS_H diff --git a/tools/qmllint/componentversion.cpp b/tools/qmllint/componentversion.cpp deleted file mode 100644 index 95403ec15f..0000000000 --- a/tools/qmllint/componentversion.cpp +++ /dev/null @@ -1,80 +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 "componentversion.h" -#include - -ComponentVersion::ComponentVersion(const QString &versionString) -{ - const int dotIdx = versionString.indexOf(QLatin1Char('.')); - if (dotIdx == -1) - return; - bool ok = false; - const int maybeMajor = versionString.leftRef(dotIdx).toInt(&ok); - if (!ok) - return; - const int maybeMinor = versionString.midRef(dotIdx + 1).toInt(&ok); - if (!ok) - return; - m_version = QTypeRevision::fromVersion(maybeMajor, maybeMinor); -} - -bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return lhs.version().majorVersion() < rhs.version().majorVersion() - || (lhs.version().majorVersion() == rhs.version().majorVersion() - && lhs.version().minorVersion() < rhs.version().minorVersion()); -} - -bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return lhs.version().majorVersion() < rhs.version().majorVersion() - || (lhs.version().majorVersion() == rhs.version().majorVersion() - && lhs.version().minorVersion() <= rhs.version().minorVersion()); -} - -bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return rhs < lhs; -} - -bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return rhs <= lhs; -} - -bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return lhs.version().majorVersion() == rhs.version().majorVersion() - && lhs.version().minorVersion() == rhs.version().minorVersion(); -} - -bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return !(lhs == rhs); -} diff --git a/tools/qmllint/componentversion.h b/tools/qmllint/componentversion.h deleted file mode 100644 index bbb039fc40..0000000000 --- a/tools/qmllint/componentversion.h +++ /dev/null @@ -1,66 +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$ -** -****************************************************************************/ - -#ifndef COMPONENTVERSION_H -#define COMPONENTVERSION_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#include -#include - -class ComponentVersion -{ -public: - ComponentVersion() = default; - ComponentVersion(QTypeRevision version) : m_version(version) {} - explicit ComponentVersion(const QString &versionString); - - QTypeRevision version() const { return m_version; } - bool isValid() const { return m_version.hasMajorVersion() && m_version.hasMinorVersion(); } - -private: - QTypeRevision m_version; -}; - -bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs); -bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs); -bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs); -bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs); -bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs); -bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs); - -#endif // COMPONENTVERSION_H diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index b28b2a4972..24d133dd81 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -30,6 +30,7 @@ #include "importedmembersvisitor.h" #include "scopetree.h" #include "typedescriptionreader.h" +#include "checkidentifiers.h" #include #include @@ -528,7 +529,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *catchStatement) { enterEnvironment(ScopeType::JSLexicalScope, "catch"); m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(), - QQmlJS::AST::VariableScope::Let); + ScopeType::JSLexicalScope); return true; } @@ -675,10 +676,10 @@ FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList qmltypeDirs, QStr *globalName != nullptr; ++globalName) { m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), - QQmlJS::AST::VariableScope::Const); + ScopeType::JSLexicalScope); } for (const auto& jsGlobVar: jsGlobVars) - m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const); + m_currentScope->insertJSIdentifier(jsGlobVar, ScopeType::JSLexicalScope); } bool FindUnqualifiedIDVisitor::check() @@ -694,15 +695,19 @@ bool FindUnqualifiedIDVisitor::check() QScopedValueRollback rollback(m_currentScope, outstandingConnection.scope); outstandingConnection.uiod->initializer->accept(this); } - return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_exportedName2Scope, - m_rootScope.get(), m_rootId, m_colorOut); + + CheckIdentifiers check(&m_colorOut, m_code, m_exportedName2Scope); + return check(m_qmlid2scope, m_rootScope.get(), m_rootId); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) { while (vdl) { - m_currentScope->insertJSIdentifier(vdl->declaration->bindingIdentifier.toString(), - vdl->declaration->scope); + m_currentScope->insertJSIdentifier( + vdl->declaration->bindingIdentifier.toString(), + (vdl->declaration->scope == QQmlJS::AST::VariableScope::Var) + ? ScopeType::JSFunctionScope + : ScopeType::JSLexicalScope); vdl = vdl->next; } return true; @@ -716,7 +721,7 @@ void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::Functi if (m_currentScope->scopeType() == ScopeType::QMLScope) m_currentScope->addMethod(MetaMethod(name, QLatin1String("void"))); else - m_currentScope->insertJSIdentifier(name, VariableScope::Const); + m_currentScope->insertJSIdentifier(name, ScopeType::JSLexicalScope); enterEnvironment(ScopeType::JSFunctionScope, name); } else { enterEnvironment(ScopeType::JSFunctionScope, QLatin1String("")); @@ -747,9 +752,8 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *) bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FormalParameterList *fpl) { - for (auto const &boundName : fpl->boundNames()) { - m_currentScope->insertJSIdentifier(boundName.id, QQmlJS::AST::VariableScope::Const); - } + for (auto const &boundName : fpl->boundNames()) + m_currentScope->insertJSIdentifier(boundName.id, ScopeType::JSLexicalScope); return true; } @@ -895,8 +899,12 @@ bool FindUnqualifiedIDVisitor::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); + for (const auto &name : names) { + m_currentScope->insertJSIdentifier( + name.id, (element->scope == QQmlJS::AST::VariableScope::Var) + ? ScopeType::JSFunctionScope + : ScopeType::JSLexicalScope); + } } return true; diff --git a/tools/qmllint/metatypes.h b/tools/qmllint/metatypes.h deleted file mode 100644 index d67de2edcd..0000000000 --- a/tools/qmllint/metatypes.h +++ /dev/null @@ -1,155 +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$ -** -****************************************************************************/ - -#ifndef METATYPES_H -#define METATYPES_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#include -#include - -class MetaEnum -{ - QString m_name; - QStringList m_keys; - -public: - MetaEnum() = default; - explicit MetaEnum(QString name) : m_name(std::move(name)) {} - - bool isValid() const { return !m_name.isEmpty(); } - - QString name() const { return m_name; } - void setName(const QString &name) { m_name = name; } - - void addKey(const QString &key) { m_keys.append(key); } - QStringList keys() const { return m_keys; } -}; - -class MetaMethod -{ -public: - enum Type { - Signal, - Slot, - Method - }; - - enum Access { - Private, - Protected, - Public - }; - - MetaMethod() = default; - explicit MetaMethod(QString name, QString returnType = QString()) - : m_name(std::move(name)) - , m_returnType(std::move(returnType)) - , m_methodType(Method) - , m_methodAccess(Public) - {} - - QString methodName() const { return m_name; } - void setMethodName(const QString &name) { m_name = name; } - - void setReturnType(const QString &type) { m_returnType = type; } - - QStringList parameterNames() const { return m_paramNames; } - QStringList parameterTypes() const { return m_paramTypes; } - void addParameter(const QString &name, const QString &type) - { - m_paramNames.append(name); - m_paramTypes.append(type); - } - - int methodType() const { return m_methodType; } - void setMethodType(Type methodType) { m_methodType = methodType; } - - Access access() const { return m_methodAccess; } - - int revision() const { return m_revision; } - void setRevision(int r) { m_revision = r; } - -private: - QString m_name; - QString m_returnType; - QStringList m_paramNames; - QStringList m_paramTypes; - Type m_methodType = Signal; - Access m_methodAccess = Private; - int m_revision = 0; -}; - -class ScopeTree; -class MetaProperty -{ - QString m_propertyName; - QString m_typeName; - const ScopeTree *m_type = nullptr; - bool m_isList; - bool m_isWritable; - bool m_isPointer; - bool m_isAlias; - int m_revision; - -public: - MetaProperty(QString propertyName, QString typeName, - bool isList, bool isWritable, bool isPointer, bool isAlias, - int revision) - : m_propertyName(std::move(propertyName)) - , m_typeName(std::move(typeName)) - , m_isList(isList) - , m_isWritable(isWritable) - , m_isPointer(isPointer) - , m_isAlias(isAlias) - , m_revision(revision) - {} - - QString propertyName() const { return m_propertyName; } - QString typeName() const { return m_typeName; } - - void setType(const ScopeTree *type) { m_type = type; } - const ScopeTree *type() const { return m_type; } - - bool isList() const { return m_isList; } - bool isWritable() const { return m_isWritable; } - bool isPointer() const { return m_isPointer; } - bool isAlias() const { return m_isAlias; } - int revision() const { return m_revision; } -}; - -#endif // METATYPES_H diff --git a/tools/qmllint/qcoloroutput.h b/tools/qmllint/qcoloroutput.h index 92f4b47ff0..6a1acfe8b5 100644 --- a/tools/qmllint/qcoloroutput.h +++ b/tools/qmllint/qcoloroutput.h @@ -44,6 +44,15 @@ class ColorOutputPrivate; +enum MessageColors +{ + Error, + Warning, + Info, + Normal, + Hint +}; + class ColorOutput { enum diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro index 4b7ca947cf..ebe531e323 100644 --- a/tools/qmllint/qmllint.pro +++ b/tools/qmllint/qmllint.pro @@ -2,23 +2,23 @@ option(host_build) QT = core-private qmldevtools-private -SOURCES += main.cpp \ - componentversion.cpp \ +include(../shared/shared.pri) + +SOURCES += \ + $$METATYPEREADER_SOURCES \ + checkidentifiers.cpp \ + main.cpp \ findunqualified.cpp \ importedmembersvisitor.cpp \ - qcoloroutput.cpp \ - scopetree.cpp \ - typedescriptionreader.cpp + qcoloroutput.cpp QMAKE_TARGET_DESCRIPTION = QML Syntax Verifier load(qt_tool) HEADERS += \ - componentversion.h \ + $$METATYPEREADER_HEADERS \ + checkidentifiers.h \ findunqualified.h \ importedmembersvisitor.h \ - metatypes.h \ - qcoloroutput.h \ - scopetree.h \ - typedescriptionreader.h + qcoloroutput.h diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp deleted file mode 100644 index 8c5358c7a5..0000000000 --- a/tools/qmllint/scopetree.cpp +++ /dev/null @@ -1,526 +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 "scopetree.h" -#include "qcoloroutput.h" - -#include - -#include - -ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) - : m_parentScope(parentScope), m_name(std::move(name)), m_scopeType(type) {} - -ScopeTree::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name) -{ - Q_ASSERT(type != ScopeType::QMLScope - || !m_parentScope - || m_parentScope->m_scopeType == ScopeType::QMLScope - || m_parentScope->m_name == "global"); - auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this}); - m_childScopes.push_back(childScope); - return childScope; -} - -void ScopeTree::insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope) -{ - Q_ASSERT(m_scopeType != ScopeType::QMLScope); - if (scope == QQmlJS::AST::VariableScope::Var) { - auto targetScope = this; - while (targetScope->scopeType() != ScopeType::JSFunctionScope) { - targetScope = targetScope->m_parentScope; - } - targetScope->m_jsIdentifiers.insert(id); - } else { - m_jsIdentifiers.insert(id); - } -} - -void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &method, - const QQmlJS::SourceLocation &loc, - bool hasMultilineHandlerBody) -{ - Q_ASSERT(m_scopeType == ScopeType::QMLScope); - m_injectedSignalIdentifiers.insert(id, {method, loc, hasMultilineHandlerBody}); -} - -void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) -{ - addProperty(property); - MetaMethod method(property.propertyName() + QLatin1String("Changed"), "void"); - addMethod(method); -} - -void ScopeTree::addUnmatchedSignalHandler(const QString &handler, - const QQmlJS::SourceLocation &location) -{ - m_unmatchedSignalHandlers.append(qMakePair(handler, location)); -} - -bool ScopeTree::isIdInCurrentScope(const QString &id) const -{ - return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); -} - -void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::SourceLocation &location) { - m_currentFieldMember = new FieldMemberList {id, QString(), location, {}}; - m_accessedIdentifiers.push_back(std::unique_ptr(m_currentFieldMember)); -} - -void ScopeTree::accessMember(const QString &name, const QString &parentType, - const QQmlJS::SourceLocation &location) -{ - Q_ASSERT(m_currentFieldMember); - auto *fieldMember = new FieldMemberList {name, parentType, location, {}}; - m_currentFieldMember->m_child.reset(fieldMember); - m_currentFieldMember = fieldMember; -} - -void ScopeTree::resetMemberScope() -{ - m_currentFieldMember = nullptr; -} - -bool ScopeTree::isVisualRootScope() const -{ - return m_parentScope && m_parentScope->m_parentScope - && m_parentScope->m_parentScope->m_parentScope == nullptr; -} - -class IssueLocationWithContext -{ -public: - IssueLocationWithContext(const QString &code, const QQmlJS::SourceLocation &location) { - int before = std::max(0,code.lastIndexOf('\n', location.offset)); - m_beforeText = code.midRef(before + 1, int(location.offset - (before + 1))); - m_issueText = code.midRef(location.offset, location.length); - int after = code.indexOf('\n', int(location.offset + location.length)); - m_afterText = code.midRef(int(location.offset + location.length), - int(after - (location.offset+location.length))); - } - - QStringRef beforeText() const { return m_beforeText; } - QStringRef issueText() const { return m_issueText; } - QStringRef afterText() const { return m_afterText; } - -private: - QStringRef m_beforeText; - QStringRef m_issueText; - QStringRef m_afterText; -}; - -static const QStringList unknownBuiltins = { - // TODO: "string" should be added to builtins.qmltypes, and the special handling below removed - QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet - QStringLiteral("QRectF"), // TODO: should be added to builtins.qmltypes - QStringLiteral("QFont"), // TODO: should be added to builtins.qmltypes - QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values. - QStringLiteral("variant"), // Same for generic variants -}; - -bool ScopeTree::checkMemberAccess( - const QString &code, - FieldMemberList *members, - const ScopeTree *scope, - const QHash &types, - ColorOutput& colorOut) const -{ - if (!members->m_child) - return true; - - Q_ASSERT(scope != nullptr); - - const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name(); - const auto &access = members->m_child; - - const auto scopeIt = scope->m_properties.find(access->m_name); - if (scopeIt != scope->m_properties.end()) { - const QString typeName = access->m_parentType.isEmpty() ? scopeIt->typeName() - : access->m_parentType; - if (scopeIt->isList() || typeName == QLatin1String("string")) { - if (access->m_child && access->m_child->m_name != QLatin1String("length")) { - colorOut.write("Warning: ", Warning); - colorOut.write( - QString::fromLatin1( - "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n") - .arg(access->m_name) - .arg(QLatin1String(scopeIt->isList() ? "list" : "string")) - .arg(access->m_child->m_name) - .arg(access->m_child->m_location.startLine) - .arg(access->m_child->m_location.startColumn), Normal); - printContext(colorOut, code, access->m_child->m_location); - return false; - } - return true; - } - - if (!access->m_child) - return true; - - if (const ScopeTree *type = scopeIt->type()) { - if (access->m_parentType.isEmpty()) - return checkMemberAccess(code, access.get(), type, types, colorOut); - } - - if (unknownBuiltins.contains(typeName)) - return true; - - const auto it = types.find(typeName); - if (it != types.end()) - return checkMemberAccess(code, access.get(), it->get(), types, colorOut); - - colorOut.write("Warning: ", Warning); - colorOut.write( - QString::fromLatin1("Type \"%1\" of member \"%2\" not found at %3:%4.\n") - .arg(typeName) - .arg(access->m_name) - .arg(access->m_location.startLine) - .arg(access->m_location.startColumn), Normal); - printContext(colorOut, code, access->m_location); - return false; - } - - const auto scopeMethodIt = scope->m_methods.find(access->m_name); - if (scopeMethodIt != scope->m_methods.end()) - return true; // Access to property of JS function - - for (const auto enumerator : scope->m_enums) { - for (const QString &key : enumerator.keys()) { - if (access->m_name != key) - continue; - - if (!access->m_child) - return true; - - colorOut.write("Warning: ", Warning); - colorOut.write(QString::fromLatin1( - "\"%1\" is an enum value. You cannot access \"%2\" on it at %3:%4\n") - .arg(access->m_name) - .arg(access->m_child->m_name) - .arg(access->m_child->m_location.startLine) - .arg(access->m_child->m_location.startColumn), Normal); - printContext(colorOut, code, access->m_child->m_location); - return false; - } - } - - auto type = types.value(scopeName); - while (type) { - const auto typeIt = type->m_properties.find(access->m_name); - if (typeIt != type->m_properties.end()) { - const ScopeTree *propType = access->m_parentType.isEmpty() - ? typeIt->type() - : types.value(access->m_parentType).get(); - return checkMemberAccess(code, access.get(), - propType ? propType : types.value(typeIt->typeName()).get(), - types, colorOut); - } - - const auto typeMethodIt = type->m_methods.find(access->m_name); - if (typeMethodIt != type->m_methods.end()) { - if (access->m_child == nullptr) - return true; - - colorOut.write("Warning: ", Warning); - colorOut.write(QString::fromLatin1( - "\"%1\" is a method. You cannot access \"%2\" on it at %3:%4\n") - .arg(access->m_name) - .arg(access->m_child->m_name) - .arg(access->m_child->m_location.startLine) - .arg(access->m_child->m_location.startColumn), Normal); - printContext(colorOut, code, access->m_child->m_location); - return false; - } - - type = types.value(type->superclassName()); - } - - if (access->m_name.front().isUpper() && scope->scopeType() == ScopeType::QMLScope) { - // may be an attached type - const auto it = types.find(access->m_name); - if (it != types.end() && !(*it)->attachedTypeName().isEmpty()) { - const auto attached = types.find((*it)->attachedTypeName()); - if (attached != types.end()) - return checkMemberAccess(code, access.get(), attached->get(), types, colorOut); - } - } - - colorOut.write("Warning: ", Warning); - colorOut.write(QString::fromLatin1( - "Property \"%1\" not found on type \"%2\" at %3:%4\n") - .arg(access->m_name) - .arg(scopeName) - .arg(access->m_location.startLine) - .arg(access->m_location.startColumn), Normal); - printContext(colorOut, code, access->m_location); - return false; -} - -bool ScopeTree::recheckIdentifiers( - const QString &code, - const QHash &qmlIDs, - const QHash &types, - const ScopeTree *root, const QString &rootId, - ColorOutput& colorOut) const -{ - bool noUnqualifiedIdentifier = true; - - // revisit all scopes - QQueue workQueue; - workQueue.enqueue(this); - while (!workQueue.empty()) { - const ScopeTree *currentScope = workQueue.dequeue(); - for (const auto &handler : currentScope->m_unmatchedSignalHandlers) { - colorOut.write("Warning: ", Warning); - colorOut.write(QString::fromLatin1( - "no matching signal found for handler \"%1\" at %2:%3\n") - .arg(handler.first).arg(handler.second.startLine) - .arg(handler.second.startColumn), Normal); - printContext(colorOut, code, handler.second); - } - - for (const auto &memberAccessTree : qAsConst(currentScope->m_accessedIdentifiers)) { - if (currentScope->isIdInCurrentJSScopes(memberAccessTree->m_name)) - continue; - - auto it = qmlIDs.find(memberAccessTree->m_name); - if (it != qmlIDs.end()) { - if (*it != nullptr) { - if (!checkMemberAccess(code, memberAccessTree.get(), *it, types, colorOut)) - noUnqualifiedIdentifier = false; - continue; - } else if (memberAccessTree->m_child - && memberAccessTree->m_child->m_name.front().isUpper()) { - // It could be a qualified type name - const QString qualified = memberAccessTree->m_name + QLatin1Char('.') - + memberAccessTree->m_child->m_name; - const auto typeIt = types.find(qualified); - if (typeIt != types.end()) { - if (!checkMemberAccess(code, memberAccessTree->m_child.get(), typeIt->get(), - types, colorOut)) { - noUnqualifiedIdentifier = false; - } - continue; - } - } - } - - auto qmlScope = currentScope->currentQMLScope(); - if (qmlScope->methods().contains(memberAccessTree->m_name)) { - // a property of a JavaScript function - continue; - } - - const auto qmlIt = qmlScope->m_properties.find(memberAccessTree->m_name); - if (qmlIt != qmlScope->m_properties.end()) { - if (!memberAccessTree->m_child || unknownBuiltins.contains(qmlIt->typeName())) - continue; - - if (!qmlIt->type()) { - colorOut.write("Warning: ", Warning); - colorOut.write(QString::fromLatin1( - "Type of property \"%2\" not found at %3:%4\n") - .arg(memberAccessTree->m_name) - .arg(memberAccessTree->m_location.startLine) - .arg(memberAccessTree->m_location.startColumn), Normal); - printContext(colorOut, code, memberAccessTree->m_location); - noUnqualifiedIdentifier = false; - } else if (!checkMemberAccess(code, memberAccessTree.get(), qmlIt->type(), types, - colorOut)) { - noUnqualifiedIdentifier = false; - } - - continue; - } - - // TODO: Lots of builtins are missing - if (memberAccessTree->m_name == "Qt") - continue; - - const auto typeIt = types.find(memberAccessTree->m_name); - if (typeIt != types.end()) { - if (!checkMemberAccess(code, memberAccessTree.get(), typeIt->get(), types, - colorOut)) { - noUnqualifiedIdentifier = false; - } - continue; - } - - noUnqualifiedIdentifier = false; - colorOut.write("Warning: ", Warning); - auto location = memberAccessTree->m_location; - colorOut.write(QString::fromLatin1("unqualified access at %1:%2\n") - .arg(location.startLine).arg(location.startColumn), - Normal); - - printContext(colorOut, code, location); - - // root(JS) --> program(qml) --> (first element) - const auto firstElement = root->m_childScopes[0]->m_childScopes[0]; - if (firstElement->m_properties.contains(memberAccessTree->m_name) - || firstElement->m_methods.contains(memberAccessTree->m_name) - || firstElement->m_enums.contains(memberAccessTree->m_name)) { - colorOut.write("Note: ", Info); - colorOut.write(memberAccessTree->m_name + QLatin1String(" is a meber of the root element\n"), Normal ); - colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); - if (rootId == QLatin1String("")) { - colorOut.write("Note: ", Warning); - colorOut.write(("You first have to give the root element an id\n")); - } - IssueLocationWithContext issueLocationWithContext {code, location}; - colorOut.write(issueLocationWithContext.beforeText().toString(), Normal); - colorOut.write(rootId + QLatin1Char('.'), Hint); - colorOut.write(issueLocationWithContext.issueText().toString(), Normal); - colorOut.write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal); - } else if (currentScope->isIdInjectedFromSignal(memberAccessTree->m_name)) { - auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers - .values(memberAccessTree->m_name); - auto location = memberAccessTree->m_location; - // sort the list of signal handlers by their occurrence in the source code - // then, we select the first one whose location is after the unqualified id - // and go one step backwards to get the one which we actually need - std::sort(methodUsages.begin(), methodUsages.end(), - [](const MethodUsage &m1, const MethodUsage &m2) { - return m1.loc.startLine < m2.loc.startLine - || (m1.loc.startLine == m2.loc.startLine - && m1.loc.startColumn < m2.loc.startColumn); - }); - auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(), - [&location](const MethodUsage &methodUsage) { - return location.startLine < methodUsage.loc.startLine - || (location.startLine == methodUsage.loc.startLine - && location.startColumn < methodUsage.loc.startColumn); - }); - auto methodUsage = *(--oneBehindIt); - colorOut.write("Note:", Info); - colorOut.write( - memberAccessTree->m_name + QString::fromLatin1( - " is accessible in this scope because " - "you are handling a signal at %1:%2\n") - .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn), - Normal); - colorOut.write("Consider using a function instead\n", Normal); - IssueLocationWithContext context {code, methodUsage.loc}; - colorOut.write(context.beforeText() + QLatin1Char(' ')); - colorOut.write(methodUsage.hasMultilineHandlerBody ? "function(" : "(", Hint); - const auto parameters = methodUsage.method.parameterNames(); - for (int numParams = parameters.size(); numParams > 0; --numParams) { - colorOut.write(parameters.at(parameters.size() - numParams), Hint); - if (numParams > 1) - colorOut.write(", ", Hint); - } - colorOut.write(methodUsage.hasMultilineHandlerBody ? ")" : ") => ", Hint); - colorOut.write(" {...", Normal); - } - colorOut.write("\n\n\n", Normal); - } - for (auto const &childScope: currentScope->m_childScopes) - workQueue.enqueue(childScope.get()); - } - return noUnqualifiedIdentifier; -} - -bool ScopeTree::isIdInCurrentQMlScopes(const QString &id) const -{ - const auto *qmlScope = currentQMLScope(); - return qmlScope->m_properties.contains(id) - || qmlScope->m_methods.contains(id) - || qmlScope->m_enums.contains(id); -} - -bool ScopeTree::isIdInCurrentJSScopes(const QString &id) const -{ - auto jsScope = this; - while (jsScope) { - if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_jsIdentifiers.contains(id)) - return true; - jsScope = jsScope->m_parentScope; - } - return false; -} - -bool ScopeTree::isIdInjectedFromSignal(const QString &id) const -{ - return currentQMLScope()->m_injectedSignalIdentifiers.contains(id); -} - -const ScopeTree *ScopeTree::currentQMLScope() const -{ - auto qmlScope = this; - while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) - qmlScope = qmlScope->m_parentScope; - return qmlScope; -} - -void ScopeTree::printContext(ColorOutput &colorOut, const QString &code, - const QQmlJS::SourceLocation &location) const -{ - IssueLocationWithContext issueLocationWithContext {code, location}; - colorOut.write(issueLocationWithContext.beforeText().toString(), Normal); - colorOut.write(issueLocationWithContext.issueText().toString(), Error); - colorOut.write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'), Normal); - int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t')); - colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText().length() - tabCount) - + QString("\t").repeated(tabCount) - + QString("^").repeated(location.length) - + QLatin1Char('\n'), Normal); -} - -void ScopeTree::addExport(const QString &name, const QString &package, - const ComponentVersion &version) -{ - m_exports.append(Export(package, name, version, 0)); -} - -void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) -{ - m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision); -} - -void ScopeTree::updateParentProperty(const ScopeTree *scope) -{ - auto it = m_properties.find(QLatin1String("parent")); - if (it != m_properties.end() - && scope->name() != QLatin1String("Component") - && scope->name() != QLatin1String("program")) - it->setType(scope); -} - -ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version, - int metaObjectRevision) : - m_package(std::move(package)), - m_type(std::move(type)), - m_version(version), - m_metaObjectRevision(metaObjectRevision) -{ -} - -bool ScopeTree::Export::isValid() const -{ - return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); -} diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h deleted file mode 100644 index 63f4310bf8..0000000000 --- a/tools/qmllint/scopetree.h +++ /dev/null @@ -1,223 +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$ -** -****************************************************************************/ - -#ifndef SCOPETREE_H -#define SCOPETREE_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#include "metatypes.h" -#include "componentversion.h" - -#include -#include - -#include -#include -#include - -enum MessageColors -{ - Error, - Warning, - Info, - Normal, - Hint -}; - -enum class ScopeType -{ - JSFunctionScope, - JSLexicalScope, - QMLScope -}; - -struct MethodUsage -{ - MetaMethod method; - QQmlJS::SourceLocation loc; - bool hasMultilineHandlerBody; -}; - -class ColorOutput; -class ScopeTree -{ - Q_DISABLE_COPY_MOVE(ScopeTree) -public: - using Ptr = QSharedPointer; - using ConstPtr = QSharedPointer; - - class Export { - public: - Export() = default; - Export(QString package, QString type, const ComponentVersion &version, - int metaObjectRevision); - - bool isValid() const; - - int metaObjectRevision() const { return m_metaObjectRevision; } - void setMetaObjectRevision(int metaObjectRevision) - { - m_metaObjectRevision = metaObjectRevision; - } - - QString package() const { return m_package; } - QString type() const { return m_type; } - - private: - QString m_package; - QString m_type; - ComponentVersion m_version; - int m_metaObjectRevision = 0; - }; - - ScopeTree(ScopeType type, QString name = QString(), - ScopeTree *parentScope = nullptr); - - ScopeTree::Ptr createNewChildScope(ScopeType type, const QString &name); - ScopeTree *parentScope() const { return m_parentScope; } - - void insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope); - void insertSignalIdentifier(const QString &id, const MetaMethod &method, - const QQmlJS::SourceLocation &loc, bool hasMultilineHandlerBody); - // inserts property as qml identifier as well as the corresponding - void insertPropertyIdentifier(const MetaProperty &prop); - void addUnmatchedSignalHandler(const QString &handler, - const QQmlJS::SourceLocation &location); - - bool isIdInCurrentScope(const QString &id) const; - void addIdToAccessed(const QString &id, const QQmlJS::SourceLocation &location); - void accessMember(const QString &name, const QString &parentType, - const QQmlJS::SourceLocation &location); - void resetMemberScope(); - - bool isVisualRootScope() const; - QString name() const { return m_name; } - - bool recheckIdentifiers( - const QString &code, - const QHash &qmlIDs, - const QHash &types, - const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const; - - ScopeType scopeType() const { return m_scopeType; } - - void addMethods(const QHash &methods) { m_methods.insert(methods); } - void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); } - QHash methods() const { return m_methods; } - - void addEnum(const MetaEnum &fakeEnum) { m_enums.insert(fakeEnum.name(), fakeEnum); } - QHash enums() const { return m_enums; } - - QString className() const { return m_className; } - void setClassName(const QString &name) { m_className = name; } - - void addExport(const QString &name, const QString &package, const ComponentVersion &version); - void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision); - QList exports() const { return m_exports; } - - void setSuperclassName(const QString &superclass) { m_superName = superclass; } - QString superclassName() const { return m_superName; } - - void addProperty(const MetaProperty &prop) { m_properties.insert(prop.propertyName(), prop); } - QHash properties() const { return m_properties; } - void updateParentProperty(const ScopeTree *scope); - - QString defaultPropertyName() const { return m_defaultPropertyName; } - void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; } - - QString attachedTypeName() const { return m_attachedTypeName; } - void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; } - - bool isSingleton() const { return m_isSingleton; } - bool isCreatable() const { return m_isCreatable; } - bool isComposite() const { return m_isComposite; } - void setIsSingleton(bool value) { m_isSingleton = value; } - void setIsCreatable(bool value) { m_isCreatable = value; } - void setIsComposite(bool value) { m_isSingleton = value; } - -private: - struct FieldMemberList - { - QString m_name; - QString m_parentType; - QQmlJS::SourceLocation m_location; - std::unique_ptr m_child; - }; - - QSet m_jsIdentifiers; - QMultiHash m_injectedSignalIdentifiers; - - QHash m_methods; - QHash m_properties; - QHash m_enums; - - std::vector> m_accessedIdentifiers; - FieldMemberList *m_currentFieldMember = nullptr; - - QVector> m_unmatchedSignalHandlers; - - QVector m_childScopes; - ScopeTree *m_parentScope; - - QString m_name; - QString m_className; - QString m_superName; - - ScopeType m_scopeType = ScopeType::QMLScope; - QList m_exports; - - QString m_defaultPropertyName; - QString m_attachedTypeName; - bool m_isSingleton = false; - bool m_isCreatable = true; - bool m_isComposite = false; - - bool isIdInCurrentQMlScopes(const QString &id) const; - bool isIdInCurrentJSScopes(const QString &id) const; - bool isIdInjectedFromSignal(const QString &id) const; - const ScopeTree *currentQMLScope() const; - void printContext(ColorOutput &colorOut, const QString &code, - const QQmlJS::SourceLocation &location) const; - bool checkMemberAccess( - const QString &code, - FieldMemberList *members, - const ScopeTree *scope, - const QHash &types, - ColorOutput& colorOut) const; -}; - -#endif // SCOPETREE_H diff --git a/tools/qmllint/typedescriptionreader.cpp b/tools/qmllint/typedescriptionreader.cpp deleted file mode 100644 index cc623b8288..0000000000 --- a/tools/qmllint/typedescriptionreader.cpp +++ /dev/null @@ -1,658 +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 "typedescriptionreader.h" - -#include -#include -#include - -#include - -using namespace QQmlJS; -using namespace QQmlJS::AST; - -QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) -{ - QString result; - - for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { - if (iter != qualifiedId) - result += delimiter; - - result += iter->name; - } - - return result; -} - -bool TypeDescriptionReader::operator()( - QHash *objects, - QStringList *dependencies) -{ - Engine engine; - - Lexer lexer(&engine); - Parser parser(&engine); - - lexer.setCode(m_source, /*lineno = */ 1, /*qmlMode = */true); - - if (!parser.parse()) { - m_errorMessage = QString::fromLatin1("%1:%2: %3").arg( - QString::number(parser.errorLineNumber()), - QString::number(parser.errorColumnNumber()), - parser.errorMessage()); - return false; - } - - m_objects = objects; - m_dependencies = dependencies; - readDocument(parser.ast()); - - return m_errorMessage.isEmpty(); -} - -void TypeDescriptionReader::readDocument(UiProgram *ast) -{ - if (!ast) { - addError(SourceLocation(), tr("Could not parse document.")); - return; - } - - if (!ast->headers || ast->headers->next || !cast(ast->headers->headerItem)) { - addError(SourceLocation(), tr("Expected a single import.")); - return; - } - - auto *import = cast(ast->headers->headerItem); - if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) { - addError(import->importToken, tr("Expected import of QtQuick.tooling.")); - return; - } - - if (!import->version) { - addError(import->firstSourceLocation(), tr("Import statement without version.")); - return; - } - - if (import->version->version.majorVersion() != 1) { - addError(import->version->firstSourceLocation(), - tr("Major version different from 1 not supported.")); - return; - } - - if (!ast->members || !ast->members->member || ast->members->next) { - addError(SourceLocation(), tr("Expected document to contain a single object definition.")); - return; - } - - auto *module = cast(ast->members->member); - if (!module) { - addError(SourceLocation(), tr("Expected document to contain a single object definition.")); - return; - } - - if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) { - addError(SourceLocation(), tr("Expected document to contain a Module {} member.")); - return; - } - - readModule(module); -} - -void TypeDescriptionReader::readModule(UiObjectDefinition *ast) -{ - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - auto *component = cast(member); - - auto *script = cast(member); - if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) { - readDependencies(script); - continue; - } - - QString typeName; - if (component) - typeName = toString(component->qualifiedTypeNameId); - - if (!component || typeName != QLatin1String("Component")) { - continue; - } - - if (typeName == QLatin1String("Component")) - readComponent(component); - } -} - -void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &message) -{ - m_errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( - QDir::toNativeSeparators(m_fileName), - QString::number(loc.startLine), - QString::number(loc.startColumn), - message); -} - -void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message) -{ - m_warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( - QDir::toNativeSeparators(m_fileName), - QString::number(loc.startLine), - QString::number(loc.startColumn), - message); -} - -void TypeDescriptionReader::readDependencies(UiScriptBinding *ast) -{ - auto *stmt = cast(ast->statement); - if (!stmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions")); - return; - } - auto *exp = cast(stmt->expression); - if (!exp) { - addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions")); - return; - } - for (PatternElementList *l = exp->elements; l; l = l->next) { - auto *str = cast(l->element->initializer); - *m_dependencies << str->value.toString(); - } -} - -void TypeDescriptionReader::readComponent(UiObjectDefinition *ast) -{ - ScopeTree::Ptr scope(new ScopeTree(ScopeType::QMLScope)); - - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - auto *component = cast(member); - auto *script = cast(member); - if (component) { - QString name = toString(component->qualifiedTypeNameId); - if (name == QLatin1String("Property")) - readProperty(component, scope); - else if (name == QLatin1String("Method") || name == QLatin1String("Signal")) - readSignalOrMethod(component, name == QLatin1String("Method"), scope); - else if (name == QLatin1String("Enum")) - readEnum(component, scope); - else - addWarning(component->firstSourceLocation(), - tr("Expected only Property, Method, Signal and Enum object definitions, " - "not \"%1\".").arg(name)); - } else if (script) { - QString name = toString(script->qualifiedId); - if (name == QLatin1String("name")) { - scope->setClassName(readStringBinding(script)); - } else if (name == QLatin1String("prototype")) { - scope->setSuperclassName(readStringBinding(script)); - } else if (name == QLatin1String("defaultProperty")) { - scope->setDefaultPropertyName(readStringBinding(script)); - } else if (name == QLatin1String("exports")) { - readExports(script, scope); - } else if (name == QLatin1String("exportMetaObjectRevisions")) { - readMetaObjectRevisions(script, scope); - } else if (name == QLatin1String("attachedType")) { - scope->setAttachedTypeName(readStringBinding(script)); - } else if (name == QLatin1String("isSingleton")) { - scope->setIsSingleton(readBoolBinding(script)); - } else if (name == QLatin1String("isCreatable")) { - scope->setIsCreatable(readBoolBinding(script)); - } else if (name == QLatin1String("isComposite")) { - scope->setIsComposite(readBoolBinding(script)); - } else { - addWarning(script->firstSourceLocation(), - tr("Expected only name, prototype, defaultProperty, attachedType, " - "exports, isSingleton, isCreatable, isComposite and " - "exportMetaObjectRevisions script bindings, not \"%1\".").arg(name)); - } - } else { - addWarning(member->firstSourceLocation(), - tr("Expected only script bindings and object definitions.")); - } - } - - if (scope->className().isEmpty()) { - addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding.")); - return; - } - - // ### add implicit export into the package of c++ types - scope->addExport(scope->className(), QStringLiteral(""), ComponentVersion()); - m_objects->insert(scope->className(), scope); -} - -void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, - const ScopeTree::Ptr &scope) -{ - MetaMethod metaMethod; - // ### confusion between Method and Slot. Method should be removed. - if (isMethod) - metaMethod.setMethodType(MetaMethod::Slot); - else - metaMethod.setMethodType(MetaMethod::Signal); - - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - auto *component = cast(member); - auto *script = cast(member); - if (component) { - QString name = toString(component->qualifiedTypeNameId); - if (name == QLatin1String("Parameter")) { - readParameter(component, &metaMethod); - } else { - addWarning(component->firstSourceLocation(), - tr("Expected only Parameter object definitions.")); - } - } else if (script) { - QString name = toString(script->qualifiedId); - if (name == QLatin1String("name")) { - metaMethod.setMethodName(readStringBinding(script)); - } else if (name == QLatin1String("type")) { - metaMethod.setReturnType(readStringBinding(script)); - } else if (name == QLatin1String("revision")) { - metaMethod.setRevision(readIntBinding(script)); - } else { - addWarning(script->firstSourceLocation(), - tr("Expected only name and type script bindings.")); - } - } else { - addWarning(member->firstSourceLocation(), - tr("Expected only script bindings and object definitions.")); - } - } - - if (metaMethod.methodName().isEmpty()) { - addError(ast->firstSourceLocation(), - tr("Method or signal is missing a name script binding.")); - return; - } - - scope->addMethod(metaMethod); -} - -void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) -{ - QString name; - QString type; - bool isPointer = false; - bool isReadonly = false; - bool isList = false; - int revision = 0; - - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - auto *script = cast(member); - if (!script) { - addWarning(member->firstSourceLocation(), tr("Expected script binding.")); - continue; - } - - QString id = toString(script->qualifiedId); - if (id == QLatin1String("name")) { - name = readStringBinding(script); - } else if (id == QLatin1String("type")) { - type = readStringBinding(script); - } else if (id == QLatin1String("isPointer")) { - isPointer = readBoolBinding(script); - } else if (id == QLatin1String("isReadonly")) { - isReadonly = readBoolBinding(script); - } else if (id == QLatin1String("isList")) { - isList = readBoolBinding(script); - } else if (id == QLatin1String("revision")) { - revision = readIntBinding(script); - } else { - addWarning(script->firstSourceLocation(), - tr("Expected only type, name, revision, isPointer, isReadonly and" - " isList script bindings.")); - } - } - - if (name.isEmpty() || type.isEmpty()) { - addError(ast->firstSourceLocation(), - tr("Property object is missing a name or type script binding.")); - return; - } - - scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, false, revision)); -} - -void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) -{ - MetaEnum metaEnum; - - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - auto *script = cast(member); - if (!script) { - addWarning(member->firstSourceLocation(), tr("Expected script binding.")); - continue; - } - - QString name = toString(script->qualifiedId); - if (name == QLatin1String("name")) { - metaEnum.setName(readStringBinding(script)); - } else if (name == QLatin1String("values")) { - readEnumValues(script, &metaEnum); - } else { - addWarning(script->firstSourceLocation(), - tr("Expected only name and values script bindings.")); - } - } - - scope->addEnum(metaEnum); -} - -void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, MetaMethod *metaMethod) -{ - QString name; - QString type; - - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - auto *script = cast(member); - if (!script) { - addWarning(member->firstSourceLocation(), tr("Expected script binding.")); - continue; - } - - const QString id = toString(script->qualifiedId); - if (id == QLatin1String("name")) { - name = readStringBinding(script); - } else if (id == QLatin1String("type")) { - type = readStringBinding(script); - } else if (id == QLatin1String("isPointer")) { - // ### unhandled - } else if (id == QLatin1String("isReadonly")) { - // ### unhandled - } else if (id == QLatin1String("isList")) { - // ### unhandled - } else { - addWarning(script->firstSourceLocation(), - tr("Expected only name and type script bindings.")); - } - } - - metaMethod->addParameter(name, type); -} - -QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast) -{ - Q_ASSERT(ast); - - if (!ast->statement) { - addError(ast->colonToken, tr("Expected string after colon.")); - return QString(); - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected string after colon.")); - return QString(); - } - - auto *stringLit = cast(expStmt->expression); - if (!stringLit) { - addError(expStmt->firstSourceLocation(), tr("Expected string after colon.")); - return QString(); - } - - return stringLit->value.toString(); -} - -bool TypeDescriptionReader::readBoolBinding(UiScriptBinding *ast) -{ - Q_ASSERT(ast); - - if (!ast->statement) { - addError(ast->colonToken, tr("Expected boolean after colon.")); - return false; - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon.")); - return false; - } - - auto *trueLit = cast(expStmt->expression); - auto *falseLit = cast(expStmt->expression); - if (!trueLit && !falseLit) { - addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon.")); - return false; - } - - return trueLit; -} - -double TypeDescriptionReader::readNumericBinding(UiScriptBinding *ast) -{ - Q_ASSERT(ast); - - if (!ast->statement) { - addError(ast->colonToken, tr("Expected numeric literal after colon.")); - return 0; - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), - tr("Expected numeric literal after colon.")); - return 0; - } - - auto *numericLit = cast(expStmt->expression); - if (!numericLit) { - addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); - return 0; - } - - return numericLit->value; -} - -ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast) -{ - ComponentVersion invalidVersion; - - if (!ast || !ast->statement) { - addError((ast ? ast->colonToken : SourceLocation()), - tr("Expected numeric literal after colon.")); - return invalidVersion; - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), - tr("Expected numeric literal after colon.")); - return invalidVersion; - } - - auto *numericLit = cast(expStmt->expression); - if (!numericLit) { - addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); - return invalidVersion; - } - - return ComponentVersion(m_source.mid(numericLit->literalToken.begin(), - numericLit->literalToken.length)); -} - -int TypeDescriptionReader::readIntBinding(UiScriptBinding *ast) -{ - double v = readNumericBinding(ast); - int i = static_cast(v); - - if (i != v) { - addError(ast->firstSourceLocation(), tr("Expected integer after colon.")); - return 0; - } - - return i; -} - -void TypeDescriptionReader::readExports(UiScriptBinding *ast, const ScopeTree::Ptr &scope) -{ - Q_ASSERT(ast); - - if (!ast->statement) { - addError(ast->colonToken, tr("Expected array of strings after colon.")); - return; - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), - tr("Expected array of strings after colon.")); - return; - } - - auto *arrayLit = cast(expStmt->expression); - if (!arrayLit) { - addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon.")); - return; - } - - for (PatternElementList *it = arrayLit->elements; it; it = it->next) { - auto *stringLit = cast(it->element->initializer); - if (!stringLit) { - addError(arrayLit->firstSourceLocation(), - tr("Expected array literal with only string literal members.")); - return; - } - QString exp = stringLit->value.toString(); - int slashIdx = exp.indexOf(QLatin1Char('/')); - int spaceIdx = exp.indexOf(QLatin1Char(' ')); - ComponentVersion version(exp.mid(spaceIdx + 1)); - - if (spaceIdx == -1 || !version.isValid()) { - addError(stringLit->firstSourceLocation(), - tr("Expected string literal to contain 'Package/Name major.minor' " - "or 'Name major.minor'.")); - continue; - } - QString package; - if (slashIdx != -1) - package = exp.left(slashIdx); - QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1)); - - // ### relocatable exports where package is empty? - scope->addExport(name, package, version); - } -} - -void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, - const ScopeTree::Ptr &scope) -{ - Q_ASSERT(ast); - - if (!ast->statement) { - addError(ast->colonToken, tr("Expected array of numbers after colon.")); - return; - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), - tr("Expected array of numbers after colon.")); - return; - } - - auto *arrayLit = cast(expStmt->expression); - if (!arrayLit) { - addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon.")); - return; - } - - int exportIndex = 0; - const int exportCount = scope->exports().size(); - for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) { - auto *numberLit = cast(it->element->initializer); - if (!numberLit) { - addError(arrayLit->firstSourceLocation(), - tr("Expected array literal with only number literal members.")); - return; - } - - if (exportIndex >= exportCount) { - addError(numberLit->firstSourceLocation(), - tr("Meta object revision without matching export.")); - return; - } - - const double v = numberLit->value; - const int metaObjectRevision = static_cast(v); - if (metaObjectRevision != v) { - addError(numberLit->firstSourceLocation(), tr("Expected integer.")); - return; - } - - scope->setExportMetaObjectRevision(exportIndex, metaObjectRevision); - } -} - -void TypeDescriptionReader::readEnumValues(UiScriptBinding *ast, MetaEnum *metaEnum) -{ - if (!ast) - return; - if (!ast->statement) { - addError(ast->colonToken, tr("Expected object literal after colon.")); - return; - } - - auto *expStmt = cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon.")); - return; - } - - if (auto *objectLit = cast(expStmt->expression)) { - for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { - if (PatternProperty *assignement = it->property) { - if (auto *name = cast(assignement->name)) { - metaEnum->addKey(name->id.toString()); - continue; - } - } - addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); - } - } else if (auto *arrayLit = cast(expStmt->expression)) { - for (PatternElementList *it = arrayLit->elements; it; it = it->next) { - if (PatternElement *element = it->element) { - if (auto *name = cast(element->initializer)) { - metaEnum->addKey(name->value.toString()); - continue; - } - } - addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); - } - } else { - addError(ast->statement->firstSourceLocation(), - tr("Expected either array or object literal as enum definition.")); - } -} diff --git a/tools/qmllint/typedescriptionreader.h b/tools/qmllint/typedescriptionreader.h deleted file mode 100644 index 2c86282163..0000000000 --- a/tools/qmllint/typedescriptionreader.h +++ /dev/null @@ -1,95 +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$ -** -****************************************************************************/ - -#ifndef TYPEDESCRIPTIONREADER_H -#define TYPEDESCRIPTIONREADER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#include "scopetree.h" - -#include - -// for Q_DECLARE_TR_FUNCTIONS -#include - -class TypeDescriptionReader -{ - Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader) -public: - TypeDescriptionReader() = default; - explicit TypeDescriptionReader(QString fileName, QString data) - : m_fileName(std::move(fileName)), m_source(std::move(data)) {} - - bool operator()( - QHash *objects, - QStringList *dependencies); - - QString errorMessage() const { return m_errorMessage; } - QString warningMessage() const { return m_warningMessage; } - -private: - void readDocument(QQmlJS::AST::UiProgram *ast); - void readModule(QQmlJS::AST::UiObjectDefinition *ast); - void readDependencies(QQmlJS::AST::UiScriptBinding *ast); - void readComponent(QQmlJS::AST::UiObjectDefinition *ast); - void readSignalOrMethod(QQmlJS::AST::UiObjectDefinition *ast, bool isMethod, - const ScopeTree::Ptr &scope); - void readProperty(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); - void readEnum(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); - void readParameter(QQmlJS::AST::UiObjectDefinition *ast, MetaMethod *metaMethod); - - QString readStringBinding(QQmlJS::AST::UiScriptBinding *ast); - bool readBoolBinding(QQmlJS::AST::UiScriptBinding *ast); - double readNumericBinding(QQmlJS::AST::UiScriptBinding *ast); - ComponentVersion readNumericVersionBinding(QQmlJS::AST::UiScriptBinding *ast); - int readIntBinding(QQmlJS::AST::UiScriptBinding *ast); - void readExports(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); - void readMetaObjectRevisions(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); - void readEnumValues(QQmlJS::AST::UiScriptBinding *ast, MetaEnum *metaEnum); - - void addError(const QQmlJS::SourceLocation &loc, const QString &message); - void addWarning(const QQmlJS::SourceLocation &loc, const QString &message); - - QString m_fileName; - QString m_source; - QString m_errorMessage; - QString m_warningMessage; - QHash *m_objects = nullptr; - QStringList *m_dependencies = nullptr; -}; - -#endif // TYPEDESCRIPTIONREADER_H diff --git a/tools/qmlplugindump/qmlplugindump.pro b/tools/qmlplugindump/qmlplugindump.pro index e374ae45f4..8bf40d2c8d 100644 --- a/tools/qmlplugindump/qmlplugindump.pro +++ b/tools/qmlplugindump/qmlplugindump.pro @@ -5,16 +5,16 @@ CONFIG += no_import_scan QTPLUGIN.platforms = qminimal -INCLUDEPATH += ../shared +include(../shared/shared.pri) SOURCES += \ + $$QMLSTREAMWRITER_SOURCES \ main.cpp \ - qmltypereader.cpp \ - ../shared/qmlstreamwriter.cpp + qmltypereader.cpp HEADERS += \ - qmltypereader.h \ - ../shared/qmlstreamwriter.h + $$QMLSTREAMWRITER_HEADERS \ + qmltypereader.h macx { # Prevent qmlplugindump from popping up in the dock when launched. diff --git a/tools/shared/componentversion.cpp b/tools/shared/componentversion.cpp new file mode 100644 index 0000000000..95403ec15f --- /dev/null +++ b/tools/shared/componentversion.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 "componentversion.h" +#include + +ComponentVersion::ComponentVersion(const QString &versionString) +{ + const int dotIdx = versionString.indexOf(QLatin1Char('.')); + if (dotIdx == -1) + return; + bool ok = false; + const int maybeMajor = versionString.leftRef(dotIdx).toInt(&ok); + if (!ok) + return; + const int maybeMinor = versionString.midRef(dotIdx + 1).toInt(&ok); + if (!ok) + return; + m_version = QTypeRevision::fromVersion(maybeMajor, maybeMinor); +} + +bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return lhs.version().majorVersion() < rhs.version().majorVersion() + || (lhs.version().majorVersion() == rhs.version().majorVersion() + && lhs.version().minorVersion() < rhs.version().minorVersion()); +} + +bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return lhs.version().majorVersion() < rhs.version().majorVersion() + || (lhs.version().majorVersion() == rhs.version().majorVersion() + && lhs.version().minorVersion() <= rhs.version().minorVersion()); +} + +bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return rhs < lhs; +} + +bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return rhs <= lhs; +} + +bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return lhs.version().majorVersion() == rhs.version().majorVersion() + && lhs.version().minorVersion() == rhs.version().minorVersion(); +} + +bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return !(lhs == rhs); +} diff --git a/tools/shared/componentversion.h b/tools/shared/componentversion.h new file mode 100644 index 0000000000..bbb039fc40 --- /dev/null +++ b/tools/shared/componentversion.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef COMPONENTVERSION_H +#define COMPONENTVERSION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include + +class ComponentVersion +{ +public: + ComponentVersion() = default; + ComponentVersion(QTypeRevision version) : m_version(version) {} + explicit ComponentVersion(const QString &versionString); + + QTypeRevision version() const { return m_version; } + bool isValid() const { return m_version.hasMajorVersion() && m_version.hasMinorVersion(); } + +private: + QTypeRevision m_version; +}; + +bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs); + +#endif // COMPONENTVERSION_H diff --git a/tools/shared/metatypes.h b/tools/shared/metatypes.h new file mode 100644 index 0000000000..d67de2edcd --- /dev/null +++ b/tools/shared/metatypes.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef METATYPES_H +#define METATYPES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include + +class MetaEnum +{ + QString m_name; + QStringList m_keys; + +public: + MetaEnum() = default; + explicit MetaEnum(QString name) : m_name(std::move(name)) {} + + bool isValid() const { return !m_name.isEmpty(); } + + QString name() const { return m_name; } + void setName(const QString &name) { m_name = name; } + + void addKey(const QString &key) { m_keys.append(key); } + QStringList keys() const { return m_keys; } +}; + +class MetaMethod +{ +public: + enum Type { + Signal, + Slot, + Method + }; + + enum Access { + Private, + Protected, + Public + }; + + MetaMethod() = default; + explicit MetaMethod(QString name, QString returnType = QString()) + : m_name(std::move(name)) + , m_returnType(std::move(returnType)) + , m_methodType(Method) + , m_methodAccess(Public) + {} + + QString methodName() const { return m_name; } + void setMethodName(const QString &name) { m_name = name; } + + void setReturnType(const QString &type) { m_returnType = type; } + + QStringList parameterNames() const { return m_paramNames; } + QStringList parameterTypes() const { return m_paramTypes; } + void addParameter(const QString &name, const QString &type) + { + m_paramNames.append(name); + m_paramTypes.append(type); + } + + int methodType() const { return m_methodType; } + void setMethodType(Type methodType) { m_methodType = methodType; } + + Access access() const { return m_methodAccess; } + + int revision() const { return m_revision; } + void setRevision(int r) { m_revision = r; } + +private: + QString m_name; + QString m_returnType; + QStringList m_paramNames; + QStringList m_paramTypes; + Type m_methodType = Signal; + Access m_methodAccess = Private; + int m_revision = 0; +}; + +class ScopeTree; +class MetaProperty +{ + QString m_propertyName; + QString m_typeName; + const ScopeTree *m_type = nullptr; + bool m_isList; + bool m_isWritable; + bool m_isPointer; + bool m_isAlias; + int m_revision; + +public: + MetaProperty(QString propertyName, QString typeName, + bool isList, bool isWritable, bool isPointer, bool isAlias, + int revision) + : m_propertyName(std::move(propertyName)) + , m_typeName(std::move(typeName)) + , m_isList(isList) + , m_isWritable(isWritable) + , m_isPointer(isPointer) + , m_isAlias(isAlias) + , m_revision(revision) + {} + + QString propertyName() const { return m_propertyName; } + QString typeName() const { return m_typeName; } + + void setType(const ScopeTree *type) { m_type = type; } + const ScopeTree *type() const { return m_type; } + + bool isList() const { return m_isList; } + bool isWritable() const { return m_isWritable; } + bool isPointer() const { return m_isPointer; } + bool isAlias() const { return m_isAlias; } + int revision() const { return m_revision; } +}; + +#endif // METATYPES_H diff --git a/tools/shared/scopetree.cpp b/tools/shared/scopetree.cpp new file mode 100644 index 0000000000..870ba13fc4 --- /dev/null +++ b/tools/shared/scopetree.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** 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 "scopetree.h" + +#include +#include + +#include + +ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) + : m_parentScope(parentScope), m_name(std::move(name)), m_scopeType(type) {} + +ScopeTree::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name) +{ + Q_ASSERT(type != ScopeType::QMLScope + || !m_parentScope + || m_parentScope->m_scopeType == ScopeType::QMLScope + || m_parentScope->m_name == QLatin1String("global")); + auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this}); + m_childScopes.push_back(childScope); + return childScope; +} + +void ScopeTree::insertJSIdentifier(const QString &id, ScopeType scope) +{ + Q_ASSERT(m_scopeType != ScopeType::QMLScope); + Q_ASSERT(scope != ScopeType::QMLScope); + if (scope == ScopeType::JSFunctionScope) { + auto targetScope = this; + while (targetScope->scopeType() != ScopeType::JSFunctionScope) + targetScope = targetScope->m_parentScope; + targetScope->m_jsIdentifiers.insert(id); + } else { + m_jsIdentifiers.insert(id); + } +} + +void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &method, + const QQmlJS::SourceLocation &loc, + bool hasMultilineHandlerBody) +{ + Q_ASSERT(m_scopeType == ScopeType::QMLScope); + m_injectedSignalIdentifiers.insert(id, {method, loc, hasMultilineHandlerBody}); +} + +void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) +{ + addProperty(property); + MetaMethod method(property.propertyName() + QLatin1String("Changed"), QLatin1String("void")); + addMethod(method); +} + +void ScopeTree::addUnmatchedSignalHandler(const QString &handler, + const QQmlJS::SourceLocation &location) +{ + m_unmatchedSignalHandlers.append(qMakePair(handler, location)); +} + +bool ScopeTree::isIdInCurrentScope(const QString &id) const +{ + return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); +} + +void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::SourceLocation &location) { + m_memberAccessChains.append(QVector()); + m_memberAccessChains.last().append(FieldMember {id, QString(), location}); +} + +void ScopeTree::accessMember(const QString &name, const QString &parentType, + const QQmlJS::SourceLocation &location) +{ + Q_ASSERT(!m_memberAccessChains.last().isEmpty()); + m_memberAccessChains.last().append(FieldMember {name, parentType, location }); +} + +bool ScopeTree::isVisualRootScope() const +{ + return m_parentScope && m_parentScope->m_parentScope + && m_parentScope->m_parentScope->m_parentScope == nullptr; +} + +bool ScopeTree::isIdInCurrentQMlScopes(const QString &id) const +{ + const auto *qmlScope = currentQMLScope(); + return qmlScope->m_properties.contains(id) + || qmlScope->m_methods.contains(id) + || qmlScope->m_enums.contains(id); +} + +bool ScopeTree::isIdInCurrentJSScopes(const QString &id) const +{ + auto jsScope = this; + while (jsScope) { + if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_jsIdentifiers.contains(id)) + return true; + jsScope = jsScope->m_parentScope; + } + return false; +} + +bool ScopeTree::isIdInjectedFromSignal(const QString &id) const +{ + return currentQMLScope()->m_injectedSignalIdentifiers.contains(id); +} + +const ScopeTree *ScopeTree::currentQMLScope() const +{ + auto qmlScope = this; + while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) + qmlScope = qmlScope->m_parentScope; + return qmlScope; +} + +void ScopeTree::addExport(const QString &name, const QString &package, + const ComponentVersion &version) +{ + m_exports.append(Export(package, name, version, 0)); +} + +void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) +{ + m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision); +} + +void ScopeTree::updateParentProperty(const ScopeTree *scope) +{ + auto it = m_properties.find(QLatin1String("parent")); + if (it != m_properties.end() + && scope->name() != QLatin1String("Component") + && scope->name() != QLatin1String("program")) + it->setType(scope); +} + +ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version, + int metaObjectRevision) : + m_package(std::move(package)), + m_type(std::move(type)), + m_version(version), + m_metaObjectRevision(metaObjectRevision) +{ +} + +bool ScopeTree::Export::isValid() const +{ + return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); +} diff --git a/tools/shared/scopetree.h b/tools/shared/scopetree.h new file mode 100644 index 0000000000..e4e6a59ac2 --- /dev/null +++ b/tools/shared/scopetree.h @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef SCOPETREE_H +#define SCOPETREE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "metatypes.h" +#include "componentversion.h" + +#include + +#include +#include +#include + +enum class ScopeType +{ + JSFunctionScope, + JSLexicalScope, + QMLScope +}; + +struct MethodUsage +{ + MetaMethod method; + QQmlJS::SourceLocation loc; + bool hasMultilineHandlerBody; +}; + +class ScopeTree +{ + Q_DISABLE_COPY_MOVE(ScopeTree) +public: + using Ptr = QSharedPointer; + using ConstPtr = QSharedPointer; + + class Export { + public: + Export() = default; + Export(QString package, QString type, const ComponentVersion &version, + int metaObjectRevision); + + bool isValid() const; + + int metaObjectRevision() const { return m_metaObjectRevision; } + void setMetaObjectRevision(int metaObjectRevision) + { + m_metaObjectRevision = metaObjectRevision; + } + + QString package() const { return m_package; } + QString type() const { return m_type; } + + private: + QString m_package; + QString m_type; + ComponentVersion m_version; + int m_metaObjectRevision = 0; + }; + + ScopeTree(ScopeType type, QString name = QString(), + ScopeTree *parentScope = nullptr); + + ScopeTree::Ptr createNewChildScope(ScopeType type, const QString &name); + ScopeTree *parentScope() const { return m_parentScope; } + + void insertJSIdentifier(const QString &id, ScopeType scope); + void insertSignalIdentifier(const QString &id, const MetaMethod &method, + const QQmlJS::SourceLocation &loc, bool hasMultilineHandlerBody); + // inserts property as qml identifier as well as the corresponding + void insertPropertyIdentifier(const MetaProperty &prop); + void addUnmatchedSignalHandler(const QString &handler, + const QQmlJS::SourceLocation &location); + + bool isIdInCurrentScope(const QString &id) const; + void addIdToAccessed(const QString &id, const QQmlJS::SourceLocation &location); + void accessMember(const QString &name, const QString &parentType, + const QQmlJS::SourceLocation &location); + + bool isVisualRootScope() const; + QString name() const { return m_name; } + + ScopeType scopeType() const { return m_scopeType; } + + void addMethods(const QHash &methods) { m_methods.insert(methods); } + void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); } + QHash methods() const { return m_methods; } + + void addEnum(const MetaEnum &fakeEnum) { m_enums.insert(fakeEnum.name(), fakeEnum); } + QHash enums() const { return m_enums; } + + QString className() const { return m_className; } + void setClassName(const QString &name) { m_className = name; } + + void addExport(const QString &name, const QString &package, const ComponentVersion &version); + void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision); + QList exports() const { return m_exports; } + + void setSuperclassName(const QString &superclass) { m_superName = superclass; } + QString superclassName() const { return m_superName; } + + void addProperty(const MetaProperty &prop) { m_properties.insert(prop.propertyName(), prop); } + QHash properties() const { return m_properties; } + void updateParentProperty(const ScopeTree *scope); + + QString defaultPropertyName() const { return m_defaultPropertyName; } + void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; } + + QString attachedTypeName() const { return m_attachedTypeName; } + void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; } + + bool isSingleton() const { return m_isSingleton; } + bool isCreatable() const { return m_isCreatable; } + bool isComposite() const { return m_isComposite; } + void setIsSingleton(bool value) { m_isSingleton = value; } + void setIsCreatable(bool value) { m_isCreatable = value; } + void setIsComposite(bool value) { m_isSingleton = value; } + + struct FieldMember + { + QString m_name; + QString m_parentType; + QQmlJS::SourceLocation m_location; + }; + + QVector> unmatchedSignalHandlers() const + { + return m_unmatchedSignalHandlers; + } + + QVector> memberAccessChains() const + { + return m_memberAccessChains; + } + + bool isIdInCurrentQMlScopes(const QString &id) const; + bool isIdInCurrentJSScopes(const QString &id) const; + bool isIdInjectedFromSignal(const QString &id) const; + const ScopeTree *currentQMLScope() const; + + QVector childScopes() const + { + return m_childScopes; + } + + QMultiHash injectedSignalIdentifiers() const + { + return m_injectedSignalIdentifiers; + } + +private: + + QSet m_jsIdentifiers; + QMultiHash m_injectedSignalIdentifiers; + + QHash m_methods; + QHash m_properties; + QHash m_enums; + + QVector> m_memberAccessChains; + QVector> m_unmatchedSignalHandlers; + + QVector m_childScopes; + ScopeTree *m_parentScope; + + QString m_name; + QString m_className; + QString m_superName; + + ScopeType m_scopeType = ScopeType::QMLScope; + QList m_exports; + + QString m_defaultPropertyName; + QString m_attachedTypeName; + bool m_isSingleton = false; + bool m_isCreatable = true; + bool m_isComposite = false; +}; + +#endif // SCOPETREE_H diff --git a/tools/shared/shared.pri b/tools/shared/shared.pri index 1438c3b3da..1b3cd4d37b 100644 --- a/tools/shared/shared.pri +++ b/tools/shared/shared.pri @@ -1,9 +1,27 @@ INCLUDEPATH += $$PWD -SOURCES += \ - $$PWD/resourcefilemapper.cpp \ +# The relevant tools need different bits and pieces. +# Furthermore, some of the classes require devtools, some not. + +RESOURCEFILEMAPPER_SOURCES = \ + $$PWD/resourcefilemapper.cpp + +RESOURCEFILEMAPPER_HEADERS = \ + $$PWD/resourcefilemapper.h + +METATYPEREADER_SOURCES = \ + $$PWD/componentversion.cpp \ + $$PWD/scopetree.cpp \ + $$PWD/typedescriptionreader.cpp + +METATYPEREADER_HEADERS = \ + $$PWD/componentversion.h \ + $$PWD/metatypes.h \ + $$PWD/scopetree.h \ + $$PWD/typedescriptionreader.h + +QMLSTREAMWRITER_SOURCES = \ $$PWD/qmlstreamwriter.cpp -HEADERS += \ - $$PWD/resourcefilemapper.h \ +QMLSTREAMWRITER_HEADERS = \ $$PWD/qmlstreamwriter.h diff --git a/tools/shared/typedescriptionreader.cpp b/tools/shared/typedescriptionreader.cpp new file mode 100644 index 0000000000..cc623b8288 --- /dev/null +++ b/tools/shared/typedescriptionreader.cpp @@ -0,0 +1,658 @@ +/**************************************************************************** +** +** 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 "typedescriptionreader.h" + +#include +#include +#include + +#include + +using namespace QQmlJS; +using namespace QQmlJS::AST; + +QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + +bool TypeDescriptionReader::operator()( + QHash *objects, + QStringList *dependencies) +{ + Engine engine; + + Lexer lexer(&engine); + Parser parser(&engine); + + lexer.setCode(m_source, /*lineno = */ 1, /*qmlMode = */true); + + if (!parser.parse()) { + m_errorMessage = QString::fromLatin1("%1:%2: %3").arg( + QString::number(parser.errorLineNumber()), + QString::number(parser.errorColumnNumber()), + parser.errorMessage()); + return false; + } + + m_objects = objects; + m_dependencies = dependencies; + readDocument(parser.ast()); + + return m_errorMessage.isEmpty(); +} + +void TypeDescriptionReader::readDocument(UiProgram *ast) +{ + if (!ast) { + addError(SourceLocation(), tr("Could not parse document.")); + return; + } + + if (!ast->headers || ast->headers->next || !cast(ast->headers->headerItem)) { + addError(SourceLocation(), tr("Expected a single import.")); + return; + } + + auto *import = cast(ast->headers->headerItem); + if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) { + addError(import->importToken, tr("Expected import of QtQuick.tooling.")); + return; + } + + if (!import->version) { + addError(import->firstSourceLocation(), tr("Import statement without version.")); + return; + } + + if (import->version->version.majorVersion() != 1) { + addError(import->version->firstSourceLocation(), + tr("Major version different from 1 not supported.")); + return; + } + + if (!ast->members || !ast->members->member || ast->members->next) { + addError(SourceLocation(), tr("Expected document to contain a single object definition.")); + return; + } + + auto *module = cast(ast->members->member); + if (!module) { + addError(SourceLocation(), tr("Expected document to contain a single object definition.")); + return; + } + + if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) { + addError(SourceLocation(), tr("Expected document to contain a Module {} member.")); + return; + } + + readModule(module); +} + +void TypeDescriptionReader::readModule(UiObjectDefinition *ast) +{ + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *component = cast(member); + + auto *script = cast(member); + if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) { + readDependencies(script); + continue; + } + + QString typeName; + if (component) + typeName = toString(component->qualifiedTypeNameId); + + if (!component || typeName != QLatin1String("Component")) { + continue; + } + + if (typeName == QLatin1String("Component")) + readComponent(component); + } +} + +void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &message) +{ + m_errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( + QDir::toNativeSeparators(m_fileName), + QString::number(loc.startLine), + QString::number(loc.startColumn), + message); +} + +void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message) +{ + m_warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( + QDir::toNativeSeparators(m_fileName), + QString::number(loc.startLine), + QString::number(loc.startColumn), + message); +} + +void TypeDescriptionReader::readDependencies(UiScriptBinding *ast) +{ + auto *stmt = cast(ast->statement); + if (!stmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions")); + return; + } + auto *exp = cast(stmt->expression); + if (!exp) { + addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions")); + return; + } + for (PatternElementList *l = exp->elements; l; l = l->next) { + auto *str = cast(l->element->initializer); + *m_dependencies << str->value.toString(); + } +} + +void TypeDescriptionReader::readComponent(UiObjectDefinition *ast) +{ + ScopeTree::Ptr scope(new ScopeTree(ScopeType::QMLScope)); + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *component = cast(member); + auto *script = cast(member); + if (component) { + QString name = toString(component->qualifiedTypeNameId); + if (name == QLatin1String("Property")) + readProperty(component, scope); + else if (name == QLatin1String("Method") || name == QLatin1String("Signal")) + readSignalOrMethod(component, name == QLatin1String("Method"), scope); + else if (name == QLatin1String("Enum")) + readEnum(component, scope); + else + addWarning(component->firstSourceLocation(), + tr("Expected only Property, Method, Signal and Enum object definitions, " + "not \"%1\".").arg(name)); + } else if (script) { + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + scope->setClassName(readStringBinding(script)); + } else if (name == QLatin1String("prototype")) { + scope->setSuperclassName(readStringBinding(script)); + } else if (name == QLatin1String("defaultProperty")) { + scope->setDefaultPropertyName(readStringBinding(script)); + } else if (name == QLatin1String("exports")) { + readExports(script, scope); + } else if (name == QLatin1String("exportMetaObjectRevisions")) { + readMetaObjectRevisions(script, scope); + } else if (name == QLatin1String("attachedType")) { + scope->setAttachedTypeName(readStringBinding(script)); + } else if (name == QLatin1String("isSingleton")) { + scope->setIsSingleton(readBoolBinding(script)); + } else if (name == QLatin1String("isCreatable")) { + scope->setIsCreatable(readBoolBinding(script)); + } else if (name == QLatin1String("isComposite")) { + scope->setIsComposite(readBoolBinding(script)); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name, prototype, defaultProperty, attachedType, " + "exports, isSingleton, isCreatable, isComposite and " + "exportMetaObjectRevisions script bindings, not \"%1\".").arg(name)); + } + } else { + addWarning(member->firstSourceLocation(), + tr("Expected only script bindings and object definitions.")); + } + } + + if (scope->className().isEmpty()) { + addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding.")); + return; + } + + // ### add implicit export into the package of c++ types + scope->addExport(scope->className(), QStringLiteral(""), ComponentVersion()); + m_objects->insert(scope->className(), scope); +} + +void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, + const ScopeTree::Ptr &scope) +{ + MetaMethod metaMethod; + // ### confusion between Method and Slot. Method should be removed. + if (isMethod) + metaMethod.setMethodType(MetaMethod::Slot); + else + metaMethod.setMethodType(MetaMethod::Signal); + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *component = cast(member); + auto *script = cast(member); + if (component) { + QString name = toString(component->qualifiedTypeNameId); + if (name == QLatin1String("Parameter")) { + readParameter(component, &metaMethod); + } else { + addWarning(component->firstSourceLocation(), + tr("Expected only Parameter object definitions.")); + } + } else if (script) { + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + metaMethod.setMethodName(readStringBinding(script)); + } else if (name == QLatin1String("type")) { + metaMethod.setReturnType(readStringBinding(script)); + } else if (name == QLatin1String("revision")) { + metaMethod.setRevision(readIntBinding(script)); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and type script bindings.")); + } + } else { + addWarning(member->firstSourceLocation(), + tr("Expected only script bindings and object definitions.")); + } + } + + if (metaMethod.methodName().isEmpty()) { + addError(ast->firstSourceLocation(), + tr("Method or signal is missing a name script binding.")); + return; + } + + scope->addMethod(metaMethod); +} + +void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) +{ + QString name; + QString type; + bool isPointer = false; + bool isReadonly = false; + bool isList = false; + int revision = 0; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + QString id = toString(script->qualifiedId); + if (id == QLatin1String("name")) { + name = readStringBinding(script); + } else if (id == QLatin1String("type")) { + type = readStringBinding(script); + } else if (id == QLatin1String("isPointer")) { + isPointer = readBoolBinding(script); + } else if (id == QLatin1String("isReadonly")) { + isReadonly = readBoolBinding(script); + } else if (id == QLatin1String("isList")) { + isList = readBoolBinding(script); + } else if (id == QLatin1String("revision")) { + revision = readIntBinding(script); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only type, name, revision, isPointer, isReadonly and" + " isList script bindings.")); + } + } + + if (name.isEmpty() || type.isEmpty()) { + addError(ast->firstSourceLocation(), + tr("Property object is missing a name or type script binding.")); + return; + } + + scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, false, revision)); +} + +void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) +{ + MetaEnum metaEnum; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + metaEnum.setName(readStringBinding(script)); + } else if (name == QLatin1String("values")) { + readEnumValues(script, &metaEnum); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and values script bindings.")); + } + } + + scope->addEnum(metaEnum); +} + +void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, MetaMethod *metaMethod) +{ + QString name; + QString type; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + const QString id = toString(script->qualifiedId); + if (id == QLatin1String("name")) { + name = readStringBinding(script); + } else if (id == QLatin1String("type")) { + type = readStringBinding(script); + } else if (id == QLatin1String("isPointer")) { + // ### unhandled + } else if (id == QLatin1String("isReadonly")) { + // ### unhandled + } else if (id == QLatin1String("isList")) { + // ### unhandled + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and type script bindings.")); + } + } + + metaMethod->addParameter(name, type); +} + +QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected string after colon.")); + return QString(); + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected string after colon.")); + return QString(); + } + + auto *stringLit = cast(expStmt->expression); + if (!stringLit) { + addError(expStmt->firstSourceLocation(), tr("Expected string after colon.")); + return QString(); + } + + return stringLit->value.toString(); +} + +bool TypeDescriptionReader::readBoolBinding(UiScriptBinding *ast) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected boolean after colon.")); + return false; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon.")); + return false; + } + + auto *trueLit = cast(expStmt->expression); + auto *falseLit = cast(expStmt->expression); + if (!trueLit && !falseLit) { + addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon.")); + return false; + } + + return trueLit; +} + +double TypeDescriptionReader::readNumericBinding(UiScriptBinding *ast) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected numeric literal after colon.")); + return 0; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected numeric literal after colon.")); + return 0; + } + + auto *numericLit = cast(expStmt->expression); + if (!numericLit) { + addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return 0; + } + + return numericLit->value; +} + +ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast) +{ + ComponentVersion invalidVersion; + + if (!ast || !ast->statement) { + addError((ast ? ast->colonToken : SourceLocation()), + tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + auto *numericLit = cast(expStmt->expression); + if (!numericLit) { + addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + return ComponentVersion(m_source.mid(numericLit->literalToken.begin(), + numericLit->literalToken.length)); +} + +int TypeDescriptionReader::readIntBinding(UiScriptBinding *ast) +{ + double v = readNumericBinding(ast); + int i = static_cast(v); + + if (i != v) { + addError(ast->firstSourceLocation(), tr("Expected integer after colon.")); + return 0; + } + + return i; +} + +void TypeDescriptionReader::readExports(UiScriptBinding *ast, const ScopeTree::Ptr &scope) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of strings after colon.")); + return; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected array of strings after colon.")); + return; + } + + auto *arrayLit = cast(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon.")); + return; + } + + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + auto *stringLit = cast(it->element->initializer); + if (!stringLit) { + addError(arrayLit->firstSourceLocation(), + tr("Expected array literal with only string literal members.")); + return; + } + QString exp = stringLit->value.toString(); + int slashIdx = exp.indexOf(QLatin1Char('/')); + int spaceIdx = exp.indexOf(QLatin1Char(' ')); + ComponentVersion version(exp.mid(spaceIdx + 1)); + + if (spaceIdx == -1 || !version.isValid()) { + addError(stringLit->firstSourceLocation(), + tr("Expected string literal to contain 'Package/Name major.minor' " + "or 'Name major.minor'.")); + continue; + } + QString package; + if (slashIdx != -1) + package = exp.left(slashIdx); + QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1)); + + // ### relocatable exports where package is empty? + scope->addExport(name, package, version); + } +} + +void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, + const ScopeTree::Ptr &scope) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of numbers after colon.")); + return; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected array of numbers after colon.")); + return; + } + + auto *arrayLit = cast(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon.")); + return; + } + + int exportIndex = 0; + const int exportCount = scope->exports().size(); + for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) { + auto *numberLit = cast(it->element->initializer); + if (!numberLit) { + addError(arrayLit->firstSourceLocation(), + tr("Expected array literal with only number literal members.")); + return; + } + + if (exportIndex >= exportCount) { + addError(numberLit->firstSourceLocation(), + tr("Meta object revision without matching export.")); + return; + } + + const double v = numberLit->value; + const int metaObjectRevision = static_cast(v); + if (metaObjectRevision != v) { + addError(numberLit->firstSourceLocation(), tr("Expected integer.")); + return; + } + + scope->setExportMetaObjectRevision(exportIndex, metaObjectRevision); + } +} + +void TypeDescriptionReader::readEnumValues(UiScriptBinding *ast, MetaEnum *metaEnum) +{ + if (!ast) + return; + if (!ast->statement) { + addError(ast->colonToken, tr("Expected object literal after colon.")); + return; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon.")); + return; + } + + if (auto *objectLit = cast(expStmt->expression)) { + for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { + if (PatternProperty *assignement = it->property) { + if (auto *name = cast(assignement->name)) { + metaEnum->addKey(name->id.toString()); + continue; + } + } + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); + } + } else if (auto *arrayLit = cast(expStmt->expression)) { + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + if (PatternElement *element = it->element) { + if (auto *name = cast(element->initializer)) { + metaEnum->addKey(name->value.toString()); + continue; + } + } + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); + } + } else { + addError(ast->statement->firstSourceLocation(), + tr("Expected either array or object literal as enum definition.")); + } +} diff --git a/tools/shared/typedescriptionreader.h b/tools/shared/typedescriptionreader.h new file mode 100644 index 0000000000..2c86282163 --- /dev/null +++ b/tools/shared/typedescriptionreader.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TYPEDESCRIPTIONREADER_H +#define TYPEDESCRIPTIONREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree.h" + +#include + +// for Q_DECLARE_TR_FUNCTIONS +#include + +class TypeDescriptionReader +{ + Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader) +public: + TypeDescriptionReader() = default; + explicit TypeDescriptionReader(QString fileName, QString data) + : m_fileName(std::move(fileName)), m_source(std::move(data)) {} + + bool operator()( + QHash *objects, + QStringList *dependencies); + + QString errorMessage() const { return m_errorMessage; } + QString warningMessage() const { return m_warningMessage; } + +private: + void readDocument(QQmlJS::AST::UiProgram *ast); + void readModule(QQmlJS::AST::UiObjectDefinition *ast); + void readDependencies(QQmlJS::AST::UiScriptBinding *ast); + void readComponent(QQmlJS::AST::UiObjectDefinition *ast); + void readSignalOrMethod(QQmlJS::AST::UiObjectDefinition *ast, bool isMethod, + const ScopeTree::Ptr &scope); + void readProperty(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); + void readEnum(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); + void readParameter(QQmlJS::AST::UiObjectDefinition *ast, MetaMethod *metaMethod); + + QString readStringBinding(QQmlJS::AST::UiScriptBinding *ast); + bool readBoolBinding(QQmlJS::AST::UiScriptBinding *ast); + double readNumericBinding(QQmlJS::AST::UiScriptBinding *ast); + ComponentVersion readNumericVersionBinding(QQmlJS::AST::UiScriptBinding *ast); + int readIntBinding(QQmlJS::AST::UiScriptBinding *ast); + void readExports(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); + void readMetaObjectRevisions(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); + void readEnumValues(QQmlJS::AST::UiScriptBinding *ast, MetaEnum *metaEnum); + + void addError(const QQmlJS::SourceLocation &loc, const QString &message); + void addWarning(const QQmlJS::SourceLocation &loc, const QString &message); + + QString m_fileName; + QString m_source; + QString m_errorMessage; + QString m_warningMessage; + QHash *m_objects = nullptr; + QStringList *m_dependencies = nullptr; +}; + +#endif // TYPEDESCRIPTIONREADER_H -- cgit v1.2.3