aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-03-30 17:42:48 +0200
committerUlf Hermann <ulf.hermann@qt.io>2020-04-01 10:29:29 +0200
commitc38ea80c5dd71a20eade7b3a3b619c1996c6af0b (patch)
treebd7eafa6e0e19f0aca3911c74ab99c7b8062a14a
parent4cf0962dc4d8d48aa600c5b56b160c8553782140 (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 <simon.hausmann@qt.io>
-rw-r--r--src/qmltyperegistrar/.prev_CMakeLists.txt1
-rw-r--r--src/qmltyperegistrar/CMakeLists.txt1
-rw-r--r--src/qmltyperegistrar/qmltyperegistrar.pro2
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp2
-rw-r--r--tools/qmlcachegen/CMakeLists.txt1
-rw-r--r--tools/qmlcachegen/qmlcachegen.pro9
-rw-r--r--tools/qmlimportscanner/.prev_CMakeLists.txt1
-rw-r--r--tools/qmlimportscanner/CMakeLists.txt1
-rw-r--r--tools/qmlimportscanner/qmlimportscanner.pro8
-rw-r--r--tools/qmllint/.prev_CMakeLists.txt11
-rw-r--r--tools/qmllint/CMakeLists.txt11
-rw-r--r--tools/qmllint/checkidentifiers.cpp408
-rw-r--r--tools/qmllint/checkidentifiers.h57
-rw-r--r--tools/qmllint/findunqualified.cpp34
-rw-r--r--tools/qmllint/qcoloroutput.h9
-rw-r--r--tools/qmllint/qmllint.pro20
-rw-r--r--tools/qmllint/scopetree.cpp526
-rw-r--r--tools/qmlplugindump/qmlplugindump.pro10
-rw-r--r--tools/shared/componentversion.cpp (renamed from tools/qmllint/componentversion.cpp)0
-rw-r--r--tools/shared/componentversion.h (renamed from tools/qmllint/componentversion.h)0
-rw-r--r--tools/shared/metatypes.h (renamed from tools/qmllint/metatypes.h)0
-rw-r--r--tools/shared/scopetree.cpp172
-rw-r--r--tools/shared/scopetree.h (renamed from tools/qmllint/scopetree.h)68
-rw-r--r--tools/shared/shared.pri26
-rw-r--r--tools/shared/typedescriptionreader.cpp (renamed from tools/qmllint/typedescriptionreader.cpp)0
-rw-r--r--tools/shared/typedescriptionreader.h (renamed from tools/qmllint/typedescriptionreader.h)0
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