diff options
author | Ulf Hermann <[email protected]> | 2020-03-30 17:42:48 +0200 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2020-04-01 10:29:29 +0200 |
commit | c38ea80c5dd71a20eade7b3a3b619c1996c6af0b (patch) | |
tree | bd7eafa6e0e19f0aca3911c74ab99c7b8062a14a | |
parent | 4cf0962dc4d8d48aa600c5b56b160c8553782140 (diff) |
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 <[email protected]>
26 files changed, 765 insertions, 613 deletions
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 <QtCore/qqueue.h> +#include <QtCore/qsharedpointer.h> + +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<ScopeTree::FieldMember> &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<QString, const ScopeTree *> &qmlIDs, + const ScopeTree *root, const QString &rootId) const +{ + bool noUnqualifiedIdentifier = true; + + // revisit all scopes + QQueue<const ScopeTree *> 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("<id>")) { + 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<QString, + ScopeTree::ConstPtr> &types) : + m_colorOut(colorOut), m_code(code), m_types(types) + {} + + bool operator ()(const QHash<QString, const ScopeTree *> &qmlIDs, + const ScopeTree *root, const QString &rootId) const; + +private: + bool checkMemberAccess(const QVector<ScopeTree::FieldMember> &members, + const ScopeTree *scope) const; + void printContext(const QQmlJS::SourceLocation &location) const; + + ColorOutput *m_colorOut = nullptr; + QString m_code; + QHash<QString, ScopeTree::ConstPtr> m_types; +}; + +#endif // CHECKIDENTIFIERS_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 <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljslexer_p.h> @@ -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<ScopeTree*> 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("<anon>")); @@ -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/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 <QtCore/qqueue.h> - -#include <algorithm> - -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<FieldMemberList>(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<QString, ScopeTree::ConstPtr> &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<QString, const ScopeTree *> &qmlIDs, - const QHash<QString, ScopeTree::ConstPtr> &types, - const ScopeTree *root, const QString &rootId, - ColorOutput& colorOut) const -{ - bool noUnqualifiedIdentifier = true; - - // revisit all scopes - QQueue<const ScopeTree *> 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("<id>")) { - 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/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/qmllint/componentversion.cpp b/tools/shared/componentversion.cpp index 95403ec15f..95403ec15f 100644 --- a/tools/qmllint/componentversion.cpp +++ b/tools/shared/componentversion.cpp diff --git a/tools/qmllint/componentversion.h b/tools/shared/componentversion.h index bbb039fc40..bbb039fc40 100644 --- a/tools/qmllint/componentversion.h +++ b/tools/shared/componentversion.h diff --git a/tools/qmllint/metatypes.h b/tools/shared/metatypes.h index d67de2edcd..d67de2edcd 100644 --- a/tools/qmllint/metatypes.h +++ b/tools/shared/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 <QtCore/qqueue.h> +#include <QtCore/qsharedpointer.h> + +#include <algorithm> + +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<FieldMember>()); + 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/qmllint/scopetree.h b/tools/shared/scopetree.h index 63f4310bf8..e4e6a59ac2 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/shared/scopetree.h @@ -42,22 +42,12 @@ #include "metatypes.h" #include "componentversion.h" -#include <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljssourcelocation_p.h> #include <QtCore/qset.h> #include <QtCore/qhash.h> #include <QtCore/qstring.h> -enum MessageColors -{ - Error, - Warning, - Info, - Normal, - Hint -}; - enum class ScopeType { JSFunctionScope, @@ -72,7 +62,6 @@ struct MethodUsage bool hasMultilineHandlerBody; }; -class ColorOutput; class ScopeTree { Q_DISABLE_COPY_MOVE(ScopeTree) @@ -110,7 +99,7 @@ public: ScopeTree::Ptr createNewChildScope(ScopeType type, const QString &name); ScopeTree *parentScope() const { return m_parentScope; } - void insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope); + 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 @@ -122,17 +111,10 @@ public: 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<QString, const ScopeTree *> &qmlIDs, - const QHash<QString, ScopeTree::ConstPtr> &types, - const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const; - ScopeType scopeType() const { return m_scopeType; } void addMethods(const QHash<QString, MetaMethod> &methods) { m_methods.insert(methods); } @@ -169,15 +151,40 @@ public: void setIsCreatable(bool value) { m_isCreatable = value; } void setIsComposite(bool value) { m_isSingleton = value; } -private: - struct FieldMemberList + struct FieldMember { QString m_name; QString m_parentType; QQmlJS::SourceLocation m_location; - std::unique_ptr<FieldMemberList> m_child; }; + QVector<QPair<QString, QQmlJS::SourceLocation>> unmatchedSignalHandlers() const + { + return m_unmatchedSignalHandlers; + } + + QVector<QVector<FieldMember>> 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<ScopeTree::Ptr> childScopes() const + { + return m_childScopes; + } + + QMultiHash<QString, MethodUsage> injectedSignalIdentifiers() const + { + return m_injectedSignalIdentifiers; + } + +private: + QSet<QString> m_jsIdentifiers; QMultiHash<QString, MethodUsage> m_injectedSignalIdentifiers; @@ -185,9 +192,7 @@ private: QHash<QString, MetaProperty> m_properties; QHash<QString, MetaEnum> m_enums; - std::vector<std::unique_ptr<FieldMemberList>> m_accessedIdentifiers; - FieldMemberList *m_currentFieldMember = nullptr; - + QVector<QVector<FieldMember>> m_memberAccessChains; QVector<QPair<QString, QQmlJS::SourceLocation>> m_unmatchedSignalHandlers; QVector<ScopeTree::Ptr> m_childScopes; @@ -205,19 +210,6 @@ private: 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<QString, ScopeTree::ConstPtr> &types, - ColorOutput& colorOut) const; }; #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/qmllint/typedescriptionreader.cpp b/tools/shared/typedescriptionreader.cpp index cc623b8288..cc623b8288 100644 --- a/tools/qmllint/typedescriptionreader.cpp +++ b/tools/shared/typedescriptionreader.cpp diff --git a/tools/qmllint/typedescriptionreader.h b/tools/shared/typedescriptionreader.h index 2c86282163..2c86282163 100644 --- a/tools/qmllint/typedescriptionreader.h +++ b/tools/shared/typedescriptionreader.h |