aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmllint
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2019-06-14 14:21:25 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2019-07-12 15:31:16 +0200
commit392521048ce6ef43a127b3dba199eee58557b1f6 (patch)
tree5dcf3c0343ecb34f299ceb468aba7f4d442d8dd7 /tools/qmllint
parentde0d91abbbcf58a66018a08ca77bb4d63a5efda1 (diff)
Extend linter to check for unqualified ids
The linter has gained a new option (-U/--check-unqualified). If run with this option, it warns about occurrences of unqualified identifiers. Furthermore, it attempts to detect the reason for why the identifier can be used unqalified: - If the id originates from the root element, it suggests to qualify the access either with the root element's id, or with "parent" if applicable. - If the id is the parameter of a signal, it suggests to use functions in the handler, instead of relying on the signal parameters to be "magically" injected into scope. The linter does not attempt to handle with statements, but warns the user instead that they are a bad idea. Change-Id: I9aaf28c37595d84886a1071d49b86799b222a617 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'tools/qmllint')
-rw-r--r--tools/qmllint/componentversion.cpp123
-rw-r--r--tools/qmllint/componentversion.h73
-rw-r--r--tools/qmllint/fakemetaobject.cpp600
-rw-r--r--tools/qmllint/fakemetaobject.h236
-rw-r--r--tools/qmllint/findunqualified.cpp772
-rw-r--r--tools/qmllint/findunqualified.h129
-rw-r--r--tools/qmllint/main.cpp42
-rw-r--r--tools/qmllint/qcoloroutput.cpp342
-rw-r--r--tools/qmllint/qcoloroutput_p.h110
-rw-r--r--tools/qmllint/qmljstypedescriptionreader.cpp704
-rw-r--r--tools/qmllint/qmljstypedescriptionreader.h103
-rw-r--r--tools/qmllint/qmllint.pro16
-rw-r--r--tools/qmllint/scopetree.cpp255
-rw-r--r--tools/qmllint/scopetree.h105
14 files changed, 3606 insertions, 4 deletions
diff --git a/tools/qmllint/componentversion.cpp b/tools/qmllint/componentversion.cpp
new file mode 100644
index 0000000000..3dc4ac37d0
--- /dev/null
+++ b/tools/qmllint/componentversion.cpp
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "componentversion.h"
+
+#include <QString>
+#include <QCryptographicHash>
+
+#include <limits>
+
+using namespace LanguageUtils;
+
+const int ComponentVersion::NoVersion = -1;
+const int ComponentVersion::MaxVersion = std::numeric_limits<int>::max();
+
+ComponentVersion::ComponentVersion()
+ : _major(NoVersion), _minor(NoVersion)
+{
+}
+
+ComponentVersion::ComponentVersion(int major, int minor)
+ : _major(major), _minor(minor)
+{
+}
+
+ComponentVersion::ComponentVersion(const QString &versionString)
+ : _major(NoVersion), _minor(NoVersion)
+{
+ int dotIdx = versionString.indexOf(QLatin1Char('.'));
+ if (dotIdx == -1)
+ return;
+ bool ok = false;
+ int maybeMajor = versionString.leftRef(dotIdx).toInt(&ok);
+ if (!ok)
+ return;
+ int maybeMinor = versionString.midRef(dotIdx + 1).toInt(&ok);
+ if (!ok)
+ return;
+ _major = maybeMajor;
+ _minor = maybeMinor;
+}
+
+ComponentVersion::~ComponentVersion()
+{
+}
+
+bool ComponentVersion::isValid() const
+{
+ return _major >= 0 && _minor >= 0;
+}
+
+QString ComponentVersion::toString() const
+{
+ return QString::fromLatin1("%1.%2").arg(QString::number(_major),
+ QString::number(_minor));
+}
+
+void ComponentVersion::addToHash(QCryptographicHash &hash) const
+{
+ hash.addData(reinterpret_cast<const char *>(&_major), sizeof(_major));
+ hash.addData(reinterpret_cast<const char *>(&_minor), sizeof(_minor));
+}
+
+namespace LanguageUtils {
+
+bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs)
+{
+ return lhs.majorVersion() < rhs.majorVersion()
+ || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() < rhs.minorVersion());
+}
+
+bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs)
+{
+ return lhs.majorVersion() < rhs.majorVersion()
+ || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() <= rhs.minorVersion());
+}
+
+bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs)
+{
+ return rhs < lhs;
+}
+
+bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs)
+{
+ return rhs <= lhs;
+}
+
+bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs)
+{
+ return lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() == rhs.minorVersion();
+}
+
+bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs)
+{
+ return !(lhs == rhs);
+}
+
+}
diff --git a/tools/qmllint/componentversion.h b/tools/qmllint/componentversion.h
new file mode 100644
index 0000000000..9d079f1d30
--- /dev/null
+++ b/tools/qmllint/componentversion.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef COMPONENTVERSION_H
+#define COMPONENTVERSION_H
+
+#include <qglobal.h>
+
+QT_BEGIN_NAMESPACE
+class QCryptographicHash;
+QT_END_NAMESPACE
+
+namespace LanguageUtils {
+
+class ComponentVersion
+{
+ int _major;
+ int _minor;
+
+public:
+ static const int NoVersion;
+ static const int MaxVersion;
+
+ ComponentVersion();
+ ComponentVersion(int major, int minor);
+ explicit ComponentVersion(const QString &versionString);
+ ~ComponentVersion();
+
+ int majorVersion() const
+ { return _major; }
+ int minorVersion() const
+ { return _minor; }
+
+ bool isValid() const;
+ QString toString() const;
+ void addToHash(QCryptographicHash &hash) const;
+};
+
+bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs);
+bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs);
+bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs);
+bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs);
+bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs);
+bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs);
+
+} // namespace LanguageUtils
+
+#endif // COMPONENTVERSION_H
diff --git a/tools/qmllint/fakemetaobject.cpp b/tools/qmllint/fakemetaobject.cpp
new file mode 100644
index 0000000000..514bb2fe42
--- /dev/null
+++ b/tools/qmllint/fakemetaobject.cpp
@@ -0,0 +1,600 @@
+/****************************************************************************
+**
+** 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 "fakemetaobject.h"
+#include <QCryptographicHash>
+
+using namespace LanguageUtils;
+
+FakeMetaEnum::FakeMetaEnum()
+{}
+
+FakeMetaEnum::FakeMetaEnum(const QString &name)
+ : m_name(name)
+{}
+
+bool FakeMetaEnum::isValid() const
+{ return !m_name.isEmpty(); }
+
+QString FakeMetaEnum::name() const
+{ return m_name; }
+
+void FakeMetaEnum::setName(const QString &name)
+{ m_name = name; }
+
+void FakeMetaEnum::addKey(const QString &key, int value)
+{ m_keys.append(key); m_values.append(value); }
+
+QString FakeMetaEnum::key(int index) const
+{ return m_keys.at(index); }
+
+int FakeMetaEnum::keyCount() const
+{ return m_keys.size(); }
+
+QStringList FakeMetaEnum::keys() const
+{ return m_keys; }
+
+bool FakeMetaEnum::hasKey(const QString &key) const
+{ return m_keys.contains(key); }
+
+void FakeMetaEnum::addToHash(QCryptographicHash &hash) const
+{
+ int len = m_name.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_name.constData()), len * sizeof(QChar));
+ len = m_keys.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ foreach (const QString &key, m_keys) {
+ len = key.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar));
+ }
+ len = m_values.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ foreach (int value, m_values)
+ hash.addData(reinterpret_cast<const char *>(&value), sizeof(value));
+}
+
+QString FakeMetaEnum::describe(int baseIndent) const
+{
+ QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
+ QString res = QLatin1String("Enum ");
+ res += name();
+ res += QLatin1String(":{");
+ for (int i = 0; i < keyCount(); ++i) {
+ res += newLine;
+ res += QLatin1String(" ");
+ res += key(i);
+ res += QLatin1String(": ");
+ res += QString::number(m_values.value(i, -1));
+ }
+ res += newLine;
+ res += QLatin1Char('}');
+ return res;
+}
+
+QString FakeMetaEnum::toString() const
+{
+ return describe();
+}
+
+FakeMetaMethod::FakeMetaMethod(const QString &name, const QString &returnType)
+ : m_name(name)
+ , m_returnType(returnType)
+ , m_methodTy(FakeMetaMethod::Method)
+ , m_methodAccess(FakeMetaMethod::Public)
+ , m_revision(0)
+{}
+
+FakeMetaMethod::FakeMetaMethod()
+ : m_methodTy(FakeMetaMethod::Method)
+ , m_methodAccess(FakeMetaMethod::Public)
+ , m_revision(0)
+{}
+
+QString FakeMetaMethod::methodName() const
+{ return m_name; }
+
+void FakeMetaMethod::setMethodName(const QString &name)
+{ m_name = name; }
+
+void FakeMetaMethod::setReturnType(const QString &type)
+{ m_returnType = type; }
+
+QStringList FakeMetaMethod::parameterNames() const
+{ return m_paramNames; }
+
+QStringList FakeMetaMethod::parameterTypes() const
+{ return m_paramTypes; }
+
+void FakeMetaMethod::addParameter(const QString &name, const QString &type)
+{ m_paramNames.append(name); m_paramTypes.append(type); }
+
+int FakeMetaMethod::methodType() const
+{ return m_methodTy; }
+
+void FakeMetaMethod::setMethodType(int methodType)
+{ m_methodTy = methodType; }
+
+int FakeMetaMethod::access() const
+{ return m_methodAccess; }
+
+int FakeMetaMethod::revision() const
+{ return m_revision; }
+
+void FakeMetaMethod::setRevision(int r)
+{ m_revision = r; }
+
+void FakeMetaMethod::addToHash(QCryptographicHash &hash) const
+{
+ int len = m_name.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_name.constData()), len * sizeof(QChar));
+ hash.addData(reinterpret_cast<const char *>(&m_methodAccess), sizeof(m_methodAccess));
+ hash.addData(reinterpret_cast<const char *>(&m_methodTy), sizeof(m_methodTy));
+ hash.addData(reinterpret_cast<const char *>(&m_revision), sizeof(m_revision));
+ len = m_paramNames.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ foreach (const QString &pName, m_paramNames) {
+ len = pName.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(pName.constData()), len * sizeof(QChar));
+ }
+ len = m_paramTypes.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ foreach (const QString &pType, m_paramTypes) {
+ len = pType.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(pType.constData()), len * sizeof(QChar));
+ }
+ len = m_returnType.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_returnType.constData()), len * sizeof(QChar));
+}
+
+QString FakeMetaMethod::describe(int baseIndent) const
+{
+ QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
+ QString res = QLatin1String("Method {");
+ res += newLine;
+ res += QLatin1String(" methodName:");
+ res += methodName();
+ res += newLine;
+ res += QLatin1String(" methodType:");
+ res += methodType();
+ res += newLine;
+ res += QLatin1String(" parameterNames:[");
+ foreach (const QString &pName, parameterNames()) {
+ res += newLine;
+ res += QLatin1String(" ");
+ res += pName;
+ }
+ res += QLatin1Char(']');
+ res += newLine;
+ res += QLatin1String(" parameterTypes:[");
+ foreach (const QString &pType, parameterTypes()) {
+ res += newLine;
+ res += QLatin1String(" ");
+ res += pType;
+ }
+ res += QLatin1Char(']');
+ res += newLine;
+ res += QLatin1Char('}');
+ return res;
+}
+
+QString FakeMetaMethod::toString() const
+{
+ return describe();
+}
+
+
+FakeMetaProperty::FakeMetaProperty(const QString &name, const QString &type, bool isList,
+ bool isWritable, bool isPointer, int revision)
+ : m_propertyName(name)
+ , m_type(type)
+ , m_isList(isList)
+ , m_isWritable(isWritable)
+ , m_isPointer(isPointer)
+ , m_revision(revision)
+{}
+
+QString FakeMetaProperty::name() const
+{ return m_propertyName; }
+
+QString FakeMetaProperty::typeName() const
+{ return m_type; }
+
+bool FakeMetaProperty::isList() const
+{ return m_isList; }
+
+bool FakeMetaProperty::isWritable() const
+{ return m_isWritable; }
+
+bool FakeMetaProperty::isPointer() const
+{ return m_isPointer; }
+
+int FakeMetaProperty::revision() const
+{ return m_revision; }
+
+void FakeMetaProperty::addToHash(QCryptographicHash &hash) const
+{
+ int len = m_propertyName.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_propertyName.constData()), len * sizeof(QChar));
+ hash.addData(reinterpret_cast<const char *>(&m_revision), sizeof(m_revision));
+ int flags = (m_isList ? (1 << 0) : 0)
+ + (m_isPointer ? (1 << 1) : 0)
+ + (m_isWritable ? (1 << 2) : 0);
+ hash.addData(reinterpret_cast<const char *>(&flags), sizeof(flags));
+ len = m_type.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_type.constData()), len * sizeof(QChar));
+}
+
+QString FakeMetaProperty::describe(int baseIndent) const
+{
+ auto boolStr = [] (bool v) { return v ? QLatin1String("true") : QLatin1String("false"); };
+ QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
+ QString res = QLatin1String("Property {");
+ res += newLine;
+ res += QLatin1String(" name:");
+ res += name();
+ res += newLine;
+ res += QLatin1String(" typeName:");
+ res += typeName();
+ res += newLine;
+ res += QLatin1String(" typeName:");
+ res += QString::number(revision());
+ res += newLine;
+ res += QLatin1String(" isList:");
+ res += boolStr(isList());
+ res += newLine;
+ res += QLatin1String(" isPointer:");
+ res += boolStr(isPointer());
+ res += newLine;
+ res += QLatin1String(" isWritable:");
+ res += boolStr(isWritable());
+ res += newLine;
+ res += QLatin1Char('}');
+ return res;
+}
+
+QString FakeMetaProperty::toString() const
+{
+ return describe();
+}
+
+
+FakeMetaObject::FakeMetaObject() : m_isSingleton(false), m_isCreatable(true), m_isComposite(false)
+{
+}
+
+QString FakeMetaObject::className() const
+{ return m_className; }
+void FakeMetaObject::setClassName(const QString &name)
+{ m_className = name; }
+
+void FakeMetaObject::addExport(const QString &name, const QString &package, ComponentVersion version)
+{
+ Export exp;
+ exp.type = name;
+ exp.package = package;
+ exp.version = version;
+ m_exports.append(exp);
+}
+
+void FakeMetaObject::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision)
+{
+ m_exports[exportIndex].metaObjectRevision = metaObjectRevision;
+}
+
+QList<FakeMetaObject::Export> FakeMetaObject::exports() const
+{ return m_exports; }
+FakeMetaObject::Export FakeMetaObject::exportInPackage(const QString &package) const
+{
+ foreach (const Export &exp, m_exports) {
+ if (exp.package == package)
+ return exp;
+ }
+ return Export();
+}
+
+void FakeMetaObject::setSuperclassName(const QString &superclass)
+{ m_superName = superclass; }
+QString FakeMetaObject::superclassName() const
+{ return m_superName; }
+
+void FakeMetaObject::addEnum(const FakeMetaEnum &fakeEnum)
+{ m_enumNameToIndex.insert(fakeEnum.name(), m_enums.size()); m_enums.append(fakeEnum); }
+int FakeMetaObject::enumeratorCount() const
+{ return m_enums.size(); }
+int FakeMetaObject::enumeratorOffset() const
+{ return 0; }
+FakeMetaEnum FakeMetaObject::enumerator(int index) const
+{ return m_enums.at(index); }
+int FakeMetaObject::enumeratorIndex(const QString &name) const
+{ return m_enumNameToIndex.value(name, -1); }
+
+void FakeMetaObject::addProperty(const FakeMetaProperty &property)
+{ m_propNameToIdx.insert(property.name(), m_props.size()); m_props.append(property); }
+int FakeMetaObject::propertyCount() const
+{ return m_props.size(); }
+int FakeMetaObject::propertyOffset() const
+{ return 0; }
+FakeMetaProperty FakeMetaObject::property(int index) const
+{ return m_props.at(index); }
+int FakeMetaObject::propertyIndex(const QString &name) const
+{ return m_propNameToIdx.value(name, -1); }
+
+void FakeMetaObject::addMethod(const FakeMetaMethod &method)
+{ m_methods.append(method); }
+int FakeMetaObject::methodCount() const
+{ return m_methods.size(); }
+int FakeMetaObject::methodOffset() const
+{ return 0; }
+FakeMetaMethod FakeMetaObject::method(int index) const
+{ return m_methods.at(index); }
+int FakeMetaObject::methodIndex(const QString &name) const //If performances becomes an issue, just use a nameToIdx hash
+{
+ for (int i=0; i<m_methods.count(); i++)
+ if (m_methods[i].methodName() == name)
+ return i;
+ return -1;
+}
+
+QString FakeMetaObject::defaultPropertyName() const
+{ return m_defaultPropertyName; }
+void FakeMetaObject::setDefaultPropertyName(const QString &defaultPropertyName)
+{ m_defaultPropertyName = defaultPropertyName; }
+
+QString FakeMetaObject::attachedTypeName() const
+{ return m_attachedTypeName; }
+void FakeMetaObject::setAttachedTypeName(const QString &name)
+{ m_attachedTypeName = name; }
+
+QByteArray FakeMetaObject::calculateFingerprint() const
+{
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ int len = m_className.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_className.constData()), len * sizeof(QChar));
+ len = m_attachedTypeName.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_attachedTypeName.constData()), len * sizeof(QChar));
+ len = m_defaultPropertyName.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_defaultPropertyName.constData()), len * sizeof(QChar));
+ len = m_enumNameToIndex.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ {
+ QStringList keys(m_enumNameToIndex.keys());
+ keys.sort();
+ foreach (const QString &key, keys) {
+ len = key.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar));
+ int value = m_enumNameToIndex.value(key);
+ hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint...
+ m_enums.at(value).addToHash(hash);
+ }
+ }
+ len = m_exports.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ foreach (const Export &e, m_exports)
+ e.addToHash(hash); // normalize order?
+ len = m_exports.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ foreach (const FakeMetaMethod &m, m_methods)
+ m.addToHash(hash); // normalize order?
+ {
+ QStringList keys(m_propNameToIdx.keys());
+ keys.sort();
+ foreach (const QString &key, keys) {
+ len = key.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar));
+ int value = m_propNameToIdx.value(key);
+ hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint...
+ m_props.at(value).addToHash(hash);
+ }
+ }
+ len = m_superName.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(m_superName.constData()), len * sizeof(QChar));
+
+ QByteArray res = hash.result();
+ res.append('F');
+ return res;
+}
+
+void FakeMetaObject::updateFingerprint()
+{
+ m_fingerprint = calculateFingerprint();
+}
+
+QByteArray FakeMetaObject::fingerprint() const
+{
+ return m_fingerprint;
+}
+
+bool FakeMetaObject::isSingleton() const
+{
+ return m_isSingleton;
+}
+
+bool FakeMetaObject::isCreatable() const
+{
+ return m_isCreatable;
+}
+
+bool FakeMetaObject::isComposite() const
+{
+ return m_isComposite;
+}
+
+void FakeMetaObject::setIsSingleton(bool value)
+{
+ m_isSingleton = value;
+}
+
+void FakeMetaObject::setIsCreatable(bool value)
+{
+ m_isCreatable = value;
+}
+
+void FakeMetaObject::setIsComposite(bool value)
+{
+ m_isSingleton = value;
+}
+
+QString FakeMetaObject::toString() const
+{
+ return describe();
+}
+
+QString FakeMetaObject::describe(bool printDetails, int baseIndent) const
+{
+ QString res = QString::fromLatin1("FakeMetaObject@%1")
+ .arg((quintptr)(void *)this, 0, 16);
+ if (!printDetails)
+ return res;
+ auto boolStr = [] (bool v) { return v ? QLatin1String("true") : QLatin1String("false"); };
+ QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
+ res += QLatin1Char('{');
+ res += newLine;
+ res += QLatin1String("className:");
+ res += className();
+ res += newLine;
+ res += QLatin1String("superClassName:");
+ res += superclassName();
+ res += newLine;
+ res += QLatin1String("isSingleton:");
+ res += boolStr(isSingleton());
+ res += newLine;
+ res += QLatin1String("isCreatable:");
+ res += boolStr(isCreatable());
+ res += newLine;
+ res += QLatin1String("isComposite:");
+ res += boolStr(isComposite());
+ res += newLine;
+ res += QLatin1String("defaultPropertyName:");
+ res += defaultPropertyName();
+ res += newLine;
+ res += QLatin1String("attachedTypeName:");
+ res += attachedTypeName();
+ res += newLine;
+ res += QLatin1String("fingerprint:");
+ res += QString::fromUtf8(fingerprint());
+
+ res += newLine;
+ res += QLatin1String("exports:[");
+ foreach (const Export &e, exports()) {
+ res += newLine;
+ res += QLatin1String(" ");
+ res += e.describe(baseIndent + 2);
+ }
+ res += QLatin1Char(']');
+
+ res += newLine;
+ res += QLatin1String("enums:[");
+ for (int iEnum = 0; iEnum < enumeratorCount() ; ++ iEnum) {
+ FakeMetaEnum e = enumerator(enumeratorOffset() + iEnum);
+ res += newLine;
+ res += QLatin1String(" ");
+ res += e.describe(baseIndent + 2);
+ }
+ res += QLatin1Char(']');
+
+ res += newLine;
+ res += QLatin1String("properties:[");
+ for (int iProp = 0; iProp < propertyCount() ; ++ iProp) {
+ FakeMetaProperty prop = property(propertyOffset() + iProp);
+ res += newLine;
+ res += QLatin1String(" ");
+ res += prop.describe(baseIndent + 2);
+ }
+ res += QLatin1Char(']');
+ res += QLatin1String("methods:[");
+ for (int iMethod = 0; iMethod < methodOffset() ; ++ iMethod) {
+ FakeMetaMethod m = method(methodOffset() + iMethod);
+ res += newLine;
+ res += QLatin1String(" ");
+ m.describe(baseIndent + 2);
+ }
+ res += QLatin1Char(']');
+ res += newLine;
+ res += QLatin1Char('}');
+ return res;
+}
+
+FakeMetaObject::Export::Export()
+ : metaObjectRevision(0)
+{}
+bool FakeMetaObject::Export::isValid() const
+{ return version.isValid() || !package.isEmpty() || !type.isEmpty(); }
+
+void FakeMetaObject::Export::addToHash(QCryptographicHash &hash) const
+{
+ int len = package.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(package.constData()), len * sizeof(QChar));
+ len = type.size();
+ hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
+ hash.addData(reinterpret_cast<const char *>(type.constData()), len * sizeof(QChar));
+ version.addToHash(hash);
+ hash.addData(reinterpret_cast<const char *>(&metaObjectRevision), sizeof(metaObjectRevision));
+}
+
+QString FakeMetaObject::Export::describe(int baseIndent) const
+{
+ QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
+ QString res = QLatin1String("Export {");
+ res += newLine;
+ res += QLatin1String(" package:");
+ res += package;
+ res += newLine;
+ res += QLatin1String(" type:");
+ res += type;
+ res += newLine;
+ res += QLatin1String(" version:");
+ res += version.toString();
+ res += newLine;
+ res += QLatin1String(" metaObjectRevision:");
+ res += QString::number(metaObjectRevision);
+ res += newLine;
+ res += QLatin1String(" isValid:");
+ res += QString::number(isValid());
+ res += newLine;
+ res += QLatin1Char('}');
+ return res;
+}
+
+QString FakeMetaObject::Export::toString() const
+{
+ return describe();
+}
diff --git a/tools/qmllint/fakemetaobject.h b/tools/qmllint/fakemetaobject.h
new file mode 100644
index 0000000000..4e0ea1f8b3
--- /dev/null
+++ b/tools/qmllint/fakemetaobject.h
@@ -0,0 +1,236 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef FAKEMETAOBJECT_H
+#define FAKEMETAOBJECT_H
+
+#include <QString>
+#include <QStringList>
+#include <QList>
+#include <QHash>
+#include <QSharedPointer>
+
+#include "componentversion.h"
+
+QT_BEGIN_NAMESPACE
+class QCryptographicHash;
+QT_END_NAMESPACE
+
+namespace LanguageUtils {
+
+class FakeMetaEnum {
+ QString m_name;
+ QStringList m_keys;
+ QList<int> m_values;
+
+public:
+ FakeMetaEnum();
+ explicit FakeMetaEnum(const QString &name);
+
+ bool isValid() const;
+
+ QString name() const;
+ void setName(const QString &name);
+
+ void addKey(const QString &key, int value);
+ QString key(int index) const;
+ int keyCount() const;
+ QStringList keys() const;
+ bool hasKey(const QString &key) const;
+ void addToHash(QCryptographicHash &hash) const;
+
+ QString describe(int baseIndent = 0) const;
+ QString toString() const;
+};
+
+class FakeMetaMethod {
+public:
+ enum {
+ Signal,
+ Slot,
+ Method
+ };
+
+ enum {
+ Private,
+ Protected,
+ Public
+ };
+
+public:
+ FakeMetaMethod();
+ explicit FakeMetaMethod(const QString &name, const QString &returnType = QString());
+
+ QString methodName() const;
+ void setMethodName(const QString &name);
+
+ void setReturnType(const QString &type);
+
+ QStringList parameterNames() const;
+ QStringList parameterTypes() const;
+ void addParameter(const QString &name, const QString &type);
+
+ int methodType() const;
+ void setMethodType(int methodType);
+
+ int access() const;
+
+ int revision() const;
+ void setRevision(int r);
+ void addToHash(QCryptographicHash &hash) const;
+
+ QString describe(int baseIndent = 0) const;
+ QString toString() const;
+private:
+ QString m_name;
+ QString m_returnType;
+ QStringList m_paramNames;
+ QStringList m_paramTypes;
+ int m_methodTy;
+ int m_methodAccess;
+ int m_revision;
+};
+
+class FakeMetaProperty {
+ QString m_propertyName;
+ QString m_type;
+ bool m_isList;
+ bool m_isWritable;
+ bool m_isPointer;
+ int m_revision;
+
+public:
+ FakeMetaProperty(const QString &name, const QString &type, bool isList, bool isWritable, bool isPointer, int revision);
+
+ QString name() const;
+ QString typeName() const;
+
+ bool isList() const;
+ bool isWritable() const;
+ bool isPointer() const;
+ int revision() const;
+ void addToHash(QCryptographicHash &hash) const;
+
+ QString describe(int baseIndent = 0) const;
+ QString toString() const;
+};
+
+class FakeMetaObject {
+ Q_DISABLE_COPY(FakeMetaObject);
+
+public:
+ typedef QSharedPointer<FakeMetaObject> Ptr;
+ typedef QSharedPointer<const FakeMetaObject> ConstPtr;
+
+ class Export {
+ public:
+ Export();
+
+ QString package;
+ QString type;
+ ComponentVersion version;
+ int metaObjectRevision;
+
+ bool isValid() const;
+ void addToHash(QCryptographicHash &hash) const;
+
+ QString describe(int baseIndent = 0) const;
+ QString toString() const;
+ };
+
+private:
+ QString m_className;
+ QList<Export> m_exports;
+ QString m_superName;
+ QList<FakeMetaEnum> m_enums;
+ QHash<QString, int> m_enumNameToIndex;
+ QList<FakeMetaProperty> m_props;
+ QHash<QString, int> m_propNameToIdx;
+ QList<FakeMetaMethod> m_methods;
+ QString m_defaultPropertyName;
+ QString m_attachedTypeName;
+ QByteArray m_fingerprint;
+ bool m_isSingleton;
+ bool m_isCreatable;
+ bool m_isComposite;
+
+public:
+ FakeMetaObject();
+
+ QString className() const;
+ void setClassName(const QString &name);
+
+ void addExport(const QString &name, const QString &package, ComponentVersion version);
+ void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision);
+ QList<Export> exports() const;
+ Export exportInPackage(const QString &package) const;
+
+ void setSuperclassName(const QString &superclass);
+ QString superclassName() const;
+
+ void addEnum(const FakeMetaEnum &fakeEnum);
+ int enumeratorCount() const;
+ int enumeratorOffset() const;
+ FakeMetaEnum enumerator(int index) const;
+ int enumeratorIndex(const QString &name) const;
+
+ void addProperty(const FakeMetaProperty &property);
+ int propertyCount() const;
+ int propertyOffset() const;
+ FakeMetaProperty property(int index) const;
+ int propertyIndex(const QString &name) const;
+
+ void addMethod(const FakeMetaMethod &method);
+ int methodCount() const;
+ int methodOffset() const;
+ FakeMetaMethod method(int index) const;
+ int methodIndex(const QString &name) const; // Note: Returns any method with that name in case of overloads
+
+ QString defaultPropertyName() const;
+ void setDefaultPropertyName(const QString &defaultPropertyName);
+
+ QString attachedTypeName() const;
+ void setAttachedTypeName(const QString &name);
+ QByteArray calculateFingerprint() const;
+ void updateFingerprint();
+ QByteArray fingerprint() const;
+
+ bool isSingleton() const;
+ bool isCreatable() const;
+ bool isComposite() const;
+ void setIsSingleton(bool value);
+ void setIsCreatable(bool value);
+ void setIsComposite(bool value);
+
+ QString describe(bool printDetails = true, int baseIndent = 0) const;
+ QString toString() const;
+};
+
+} // namespace LanguageUtils
+
+#endif // FAKEMETAOBJECT_H
diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp
new file mode 100644
index 0000000000..3abdfcfa53
--- /dev/null
+++ b/tools/qmllint/findunqualified.cpp
@@ -0,0 +1,772 @@
+/****************************************************************************
+**
+** 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 "findunqualified.h"
+#include "scopetree.h"
+
+#include "qmljstypedescriptionreader.h"
+
+#include <QFile>
+#include <QDirIterator>
+#include <QScopedValueRollback>
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsparser_p.h>
+
+QDebug &operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc);
+
+static QQmlJS::TypeDescriptionReader createReaderForFile(QString const &filename)
+{
+ QFile f(filename);
+ f.open(QFile::ReadOnly);
+ QQmlJS::TypeDescriptionReader reader { filename, f.readAll() };
+ return reader;
+}
+
+void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name)
+{
+ m_currentScope = m_currentScope->createNewChildScope(type, name);
+}
+
+void FindUnqualifiedIDVisitor::leaveEnvironment()
+{
+ m_currentScope = m_currentScope->parentScope();
+}
+
+enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned };
+
+QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin)
+{
+ static const QLatin1Char Slash('/');
+ static const QLatin1Char Backslash('\\');
+ static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes");
+
+ const QVector<QStringRef> parts = uri.splitRef(QLatin1Char('.'), QString::SkipEmptyParts);
+
+ QStringList qmlDirPathsPaths;
+ // fully & partially versioned parts + 1 unversioned for each base path
+ qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1));
+
+ auto versionString = [](int vmaj, int vmin, ImportVersion version)
+ {
+ if (version == FullyVersioned) {
+ // extension with fully encoded version number (eg. MyModule.3.2)
+ return QString::asprintf(".%d.%d", vmaj, vmin);
+ } else if (version == PartiallyVersioned) {
+ // extension with encoded version major (eg. MyModule.3)
+ return QString::asprintf(".%d", vmaj);
+ } // else extension without version number (eg. MyModule)
+ return QString();
+ };
+ auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep)
+ {
+ QString str;
+ for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
+ if (it != refs.cbegin())
+ str += sep;
+ str += *it;
+ }
+ return str;
+ };
+
+ for (int version = FullyVersioned; version <= Unversioned; ++version) {
+ const QString ver = versionString(vmaj, vmin, static_cast<ImportVersion>(version));
+
+ for (const QString &path : basePaths) {
+ QString dir = path;
+ if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
+ dir += Slash;
+
+ // append to the end
+ qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + SlashPluginsDotQmltypes;
+
+ if (version != Unversioned) {
+ // insert in the middle
+ for (int index = parts.count() - 2; index >= 0; --index) {
+ qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
+ + ver + Slash
+ + joinStringRefs(parts.mid(index + 1), Slash) + SlashPluginsDotQmltypes;
+ }
+ }
+ }
+ }
+
+ return qmlDirPathsPaths;
+}
+
+void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int major, int minor)
+{
+ QPair<QString, QString> importId { id, prefix };
+ if (m_alreadySeenImports.contains(importId)) {
+ return;
+ } else {
+ m_alreadySeenImports.insert(importId);
+ }
+ id = id.replace(QLatin1String("/"), QLatin1String("."));
+ auto qmltypesPaths = completeQmltypesPaths(id, m_qmltypeDirs, major, minor);
+
+ QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
+ QList<QQmlJS::ModuleApiInfo> moduleApis;
+ QStringList dependencies;
+ for (auto const &qmltypesPath : qmltypesPaths) {
+ if (QFile::exists(qmltypesPath)) {
+ auto reader = createReaderForFile(qmltypesPath);
+ auto succ = reader(&objects, &moduleApis, &dependencies);
+ if (!succ) {
+ qDebug() << reader.errorMessage();
+ }
+ break;
+ }
+ }
+ for (auto const &dependency : qAsConst(dependencies)) {
+ auto const split = dependency.split(" ");
+ auto const id = split.at(0);
+ auto const major = split.at(1).split('.').at(0).toInt();
+ auto const minor = split.at(1).split('.').at(1).toInt();
+ importHelper(id, QString(), major, minor);
+ }
+ // add objects
+ for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) {
+ auto val = ob_it.value();
+ m_exportedName2MetaObject[prefix + val->className()] = val;
+ for (auto export_ : val->exports()) {
+ m_exportedName2MetaObject[prefix + export_.type] = val;
+ }
+ for (auto enumCount = 0; enumCount < val->enumeratorCount(); ++enumCount) {
+ m_currentScope->insertQMLIdentifier(val->enumerator(enumCount).name());
+ }
+ }
+}
+
+LanguageUtils::FakeMetaObject *
+FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath)
+{
+ using namespace QQmlJS::AST;
+ auto fake = new LanguageUtils::FakeMetaObject;
+ fake->setClassName(QFileInfo { filePath }.baseName());
+ QFile file(filePath);
+ if (!file.open(QFile::ReadOnly)) {
+ return fake;
+ }
+ QString code = file.readAll();
+ file.close();
+
+ QQmlJS::Engine engine;
+ QQmlJS::Lexer lexer(&engine);
+
+ lexer.setCode(code, 1, true);
+ QQmlJS::Parser parser(&engine);
+ if (!parser.parse()) {
+ return fake;
+ }
+ QQmlJS::AST::UiProgram *program = parser.ast();
+ auto header = program->headers;
+ while (header) {
+ if (auto import = cast<UiImport *>(header->headerItem)) {
+ if (import->version) {
+ QString path;
+ auto uri = import->importUri;
+ while (uri) {
+ path.append(uri->name);
+ path.append("/");
+ uri = uri->next;
+ }
+ path.chop(1);
+ QString prefix = QLatin1String("");
+ if (import->asToken.isValid()) {
+ prefix += import->importId + QLatin1Char('.');
+ }
+ importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion);
+ }
+ }
+ header = header->next;
+ }
+ auto member = program->members;
+ // member should be the sole element
+ Q_ASSERT(!member->next);
+ Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition);
+ auto definition = static_cast<UiObjectDefinition *>(member->member);
+ auto qualifiedId = definition->qualifiedTypeNameId;
+ while (qualifiedId && qualifiedId->next) {
+ qualifiedId = qualifiedId->next;
+ }
+ fake->setSuperclassName(qualifiedId->name.toString());
+ UiObjectMemberList *initMembers = definition->initializer->members;
+ while (initMembers) {
+ switch (initMembers->member->kind) {
+ case UiObjectMember::Kind_UiArrayBinding: {
+ // nothing to do
+ break;
+ }
+ case UiObjectMember::Kind_UiEnumDeclaration: {
+ auto enumDeclaration = static_cast<UiEnumDeclaration *>(initMembers->member);
+ qDebug() << "enumdecl" << enumDeclaration->name;
+ break;
+ }
+ case UiObjectMember::Kind_UiObjectBinding: {
+ // nothing to do
+ break;
+ }
+ case UiObjectMember::Kind_UiObjectDefinition: {
+ // creates nothing accessible
+ /*auto objectDefinition = static_cast<UiObjectDefinition*>(initMembers->member);
+ qDebug() << "objdef" << objectDefinition->qualifiedTypeNameId->name;*/
+ break;
+ }
+ case UiObjectMember::Kind_UiPublicMember: {
+ auto publicMember = static_cast<UiPublicMember *>(initMembers->member);
+ switch (publicMember->type) {
+ case UiPublicMember::Signal: {
+ UiParameterList *param = publicMember->parameters;
+ LanguageUtils::FakeMetaMethod method;
+ method.setMethodType(LanguageUtils::FakeMetaMethod::Signal);
+ method.setMethodName(publicMember->name.toString());
+ while (param) {
+ method.addParameter(param->name.toString(), param->type->name.toString());
+ param = param->next;
+ }
+ fake->addMethod(method);
+ break;
+ }
+ case UiPublicMember::Property: {
+ LanguageUtils::FakeMetaProperty fakeprop { publicMember->name.toString(),
+ publicMember->typeModifier.toString(),
+ false,
+ false,
+ false,
+ 0 };
+ fake->addProperty(fakeprop);
+ break;
+ }
+ }
+ break;
+ }
+ case UiObjectMember::Kind_UiScriptBinding: {
+ // does not create anything new, ignore
+ break;
+ }
+ case UiObjectMember::Kind_UiSourceElement: {
+ auto sourceElement = static_cast<UiSourceElement *>(initMembers->member);
+ if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) {
+ LanguageUtils::FakeMetaMethod method;
+ method.setMethodType(LanguageUtils::FakeMetaMethod::Method);
+ FormalParameterList *parameters = fexpr->formals;
+ while (parameters) {
+ method.addParameter(parameters->element->bindingIdentifier.toString(),
+ "");
+ parameters = parameters->next;
+ }
+ fake->addMethod(method);
+ } else if (ClassExpression *clexpr =
+ sourceElement->sourceElement->asClassDefinition()) {
+ LanguageUtils::FakeMetaProperty prop {
+ clexpr->name.toString(), "", false, false, false, 1
+ };
+ fake->addProperty(prop);
+ } else if (cast<VariableStatement *>(sourceElement->sourceElement)) {
+ // nothing to do
+ } else {
+ qDebug() << "unsupportedd sourceElement at" << sourceElement->firstSourceLocation()
+ << sourceElement->sourceElement->kind;
+ }
+ break;
+ }
+ default: {
+ qDebug() << "unsupported element of kind" << initMembers->member->kind;
+ }
+ }
+ initMembers = initMembers->next;
+ }
+ return fake;
+}
+
+void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString name)
+{
+ for (;;) {
+ auto metaObject = m_exportedName2MetaObject[m_exportedName2MetaObject.contains(name)
+ ? name
+ : prefix + QLatin1Char('.') + name];
+ if (metaObject) {
+ auto propertyCount = metaObject->propertyCount();
+ for (auto i = 0; i < propertyCount; ++i) {
+ m_currentScope->insertPropertyIdentifier(metaObject->property(i).name());
+ }
+
+ m_currentScope->addMethodsFromMetaObject(metaObject);
+
+ name = metaObject->superclassName();
+ if (name.isEmpty() || name == QLatin1String("QObject")) {
+ break;
+ }
+ } else {
+ qDebug() << name << "not found";
+ break;
+ }
+ }
+}
+
+void FindUnqualifiedIDVisitor::throwRecursionDepthError()
+{
+ return;
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *)
+{
+ enterEnvironment(ScopeType::QMLScope, "program");
+ QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
+ QList<QQmlJS::ModuleApiInfo> moduleApis;
+ QStringList dependencies;
+ for (auto const &dir : m_qmltypeDirs) {
+ QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter,
+ QDirIterator::Subdirectories };
+ while (it.hasNext()) {
+ auto reader = createReaderForFile(it.next());
+ auto succ = reader(&objects, &moduleApis, &dependencies);
+ if (!succ) {
+ qDebug() << reader.errorMessage();
+ }
+ }
+ }
+ // add builtins
+ for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) {
+ auto val = ob_it.value();
+ for (auto export_ : val->exports()) {
+ m_exportedName2MetaObject[export_.type] = val;
+ }
+ for (auto enumCount = 0; enumCount < val->enumeratorCount(); ++enumCount) {
+ m_currentScope->insertQMLIdentifier(val->enumerator(enumCount).name());
+ }
+ }
+ // add "self" (as we only ever check the first part of a qualified identifier, we get away with
+ // using an empty FakeMetaObject
+ m_exportedName2MetaObject[QFileInfo { m_filePath }.baseName()] = {};
+
+ // add QML builtins
+ m_exportedName2MetaObject["QtObject"] = {}; // QtObject contains nothing of interest
+
+ LanguageUtils::FakeMetaObject *meta = new LanguageUtils::FakeMetaObject{};
+ meta->addProperty(LanguageUtils::FakeMetaProperty {"enabled", "bool", false, false, false, 0});
+ meta->addProperty(LanguageUtils::FakeMetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0});
+ meta->addProperty(LanguageUtils::FakeMetaProperty {"target", "QObject", false, false, false, 0});
+ m_exportedName2MetaObject["Connections"] = LanguageUtils::FakeMetaObject::ConstPtr { meta };
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiProgram *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassExpression *ast)
+{
+ enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString());
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassExpression *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
+{
+ enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString());
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForStatement *)
+{
+ enterEnvironment(ScopeType::JSLexicalScope, "forloop");
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForStatement *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForEachStatement *)
+{
+ enterEnvironment(ScopeType::JSLexicalScope, "foreachloop");
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Block *)
+{
+ enterEnvironment(ScopeType::JSLexicalScope, "block");
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Block *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::CaseBlock *)
+{
+ enterEnvironment(ScopeType::JSLexicalScope, "case");
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::CaseBlock *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *)
+{
+ enterEnvironment(ScopeType::JSLexicalScope, "catch");
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::WithStatement *withStatement)
+{
+ m_colorOut.write(QString::asprintf("Warning: "), Warning);
+ m_colorOut.write(QString::asprintf("%d:%d: with statements are strongly discouraged in QML and might cause false positives when analying unqalified identifiers\n", withStatement->firstSourceLocation().startLine, withStatement->firstSourceLocation().startColumn), Normal);
+ enterEnvironment(ScopeType::JSLexicalScope, "with");
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
+{
+ using namespace QQmlJS::AST;
+ auto name = uisb->qualifiedId->name;
+ if (name == QLatin1String("id")) {
+ // found id
+ auto expstat = static_cast<ExpressionStatement *>(uisb->statement);
+ auto identexp = static_cast<IdentifierExpression *>(expstat->expression);
+ QString elementName = m_currentScope->name();
+ m_qmlid2meta.insert(identexp->name.toString(), m_exportedName2MetaObject[elementName]);
+ if (m_currentScope->isVisualRootScope()) {
+ m_rootId = identexp->name.toString();
+ }
+ } else if (name.startsWith("on") && name.size() > 2 && name.at(2).isUpper()) {
+ auto statement = uisb->statement;
+ if (statement->kind == Node::Kind::Kind_ExpressionStatement) {
+ if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
+ // functions are already handled
+ // they do not get names inserted according to the signal, but access their formal
+ // parameters
+ return true;
+ }
+ }
+ QString signal = name.mid(2).toString();
+ signal[0] = signal[0].toLower();
+ if (!m_currentScope->methods().contains(signal)) {
+ qDebug() << "Info file does not contain signal" << signal;
+ } else {
+ auto method = m_currentScope->methods()[signal];
+ for (auto const &param : method.parameterNames()) {
+ m_currentScope->insertSignalIdentifier(param, method, uisb->statement->firstSourceLocation());
+ }
+ }
+ return true;
+ }
+ return true;
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm)
+{
+ // property bool inactive: !active
+ // extract name inactive
+ m_currentScope->insertPropertyIdentifier(uipm->name.toString());
+ return true;
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
+{
+ auto name = idexp->name;
+ if (!m_exportedName2MetaObject.contains(name.toString())) {
+ m_currentScope->addIdToAccssedIfNotInParentScopes(
+ { name.toString(), idexp->firstSourceLocation() });
+ }
+ return true;
+}
+
+FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs,
+ const QString &code, const QString &fileName)
+ : m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global" }),
+ m_currentScope(m_rootScope.get()),
+ m_qmltypeDirs(qmltypeDirs),
+ m_code(code),
+ m_rootId(QLatin1String("<id>")),
+ m_filePath(fileName)
+{
+ // setup color output
+ m_colorOut.insertColorMapping(Error, ColorOutput::RedForeground);
+ m_colorOut.insertColorMapping(Warning, ColorOutput::PurpleForeground);
+ m_colorOut.insertColorMapping(Info, ColorOutput::BlueForeground);
+ m_colorOut.insertColorMapping(Normal, ColorOutput::DefaultColor);
+ m_colorOut.insertColorMapping(Hint, ColorOutput::GreenForeground);
+ QLatin1String jsGlobVars[] = {
+ QLatin1String ("Array"), QLatin1String("Boolean"), QLatin1String("Date"), QLatin1String("Function"), QLatin1String("Math"), QLatin1String("Number"), QLatin1String("Object"), QLatin1String("RegExp"), QLatin1String("String"),
+ QLatin1String("Error"), QLatin1String("EvalError"), QLatin1String("RangeError"), QLatin1String("ReferenceError"), QLatin1String("SyntaxError"), QLatin1String("TypeError"), QLatin1String("URIError"),
+ QLatin1String("encodeURI"), QLatin1String("encodeURIComponent"), QLatin1String("decodeURI"), QLatin1String("decodeURIComponent"), QLatin1String("escape"), QLatin1String("unescape"),
+ QLatin1String("isFinite"), QLatin1String("isNanN"), QLatin1String("parseFloat"), QLatin1String("parseInt"),
+ QLatin1String("eval"), QLatin1String("console"), QLatin1String("print"), QLatin1String("gc"),
+ QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
+ QLatin1String("XMLHttpRequest"), QLatin1String("JSON"), QLatin1String("Promise"),
+ QLatin1String("undefined")
+ };
+ for (const auto& jsGlobVar: jsGlobVars)
+ m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const);
+}
+
+FindUnqualifiedIDVisitor::~FindUnqualifiedIDVisitor() = default;
+
+bool FindUnqualifiedIDVisitor::check()
+{
+ // now that all ids are known, revisit any Connections whose target were perviously unknown
+ for (auto const& outstandingConnection: m_outstandingConnections) {
+ auto metaObject = m_qmlid2meta[outstandingConnection.targetName];
+ outstandingConnection.scope->addMethodsFromMetaObject(metaObject);
+ QScopedValueRollback<ScopeTree*> rollback(m_currentScope, outstandingConnection.scope);
+ outstandingConnection.uiod->initializer->accept(this);
+ }
+ return m_rootScope->recheckIdentifiers(m_code, m_qmlid2meta, m_rootScope.get(), m_rootId, m_colorOut);
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
+{
+ while (vdl) {
+ m_currentScope->insertJSIdentifier(vdl->declaration->bindingIdentifier.toString(),
+ vdl->declaration->scope);
+ vdl = vdl->next;
+ }
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr)
+{
+ using namespace QQmlJS::AST;
+ if (!fexpr->name.isEmpty()) {
+ auto name = fexpr->name.toString();
+ if (m_currentScope->scopeType() == ScopeType::QMLScope) {
+ m_currentScope->insertQMLIdentifier(name);
+ } else {
+ m_currentScope->insertJSIdentifier(name, VariableScope::Const);
+ }
+ }
+ // qDebug() << fexpr->firstSourceLocation() << "function expression" << fexpr->name;
+ QString name = fexpr->name.toString();
+ if (name.isEmpty())
+ name = "<anon>";
+ enterEnvironment(ScopeType::JSFunctionScope, name);
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
+{
+ visitFunctionExpressionHelper(fexpr);
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionExpression *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl)
+{
+ visitFunctionExpressionHelper(fdecl);
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
+{
+ for (auto const &boundName : fpl->boundNames()) {
+ m_currentScope->insertJSIdentifier(boundName.id, QQmlJS::AST::VariableScope::Const);
+ }
+ return true;
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import)
+{
+ // construct path
+ QString prefix = QLatin1String("");
+ if (import->asToken.isValid()) {
+ prefix += import->importId + QLatin1Char('.');
+ }
+ auto dirname = import->fileName.toString();
+ if (!dirname.isEmpty()) {
+ QFileInfo info { dirname };
+ if (info.isRelative()) {
+ dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname);
+ }
+ QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter };
+ while (it.hasNext()) {
+ LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next());
+ m_exportedName2MetaObject.insert(
+ fake->className(), QSharedPointer<const LanguageUtils::FakeMetaObject>(fake));
+ }
+ }
+ QString path {};
+ if (!import->importId.isEmpty()) {
+ m_qmlid2meta.insert(import->importId.toString(), {}); // TODO: do not put imported ids into the same space as qml IDs
+ }
+ if (import->version) {
+ auto uri = import->importUri;
+ while (uri) {
+ path.append(uri->name);
+ path.append("/");
+ uri = uri->next;
+ }
+ path.chop(1);
+
+ importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion);
+ }
+ return true;
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
+{
+ m_currentScope->insertQMLIdentifier(uied->name.toString());
+ return true;
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
+{
+ // property QtObject __styleData: QtObject {...}
+ m_currentScope->insertPropertyIdentifier(uiob->qualifiedId->name.toString());
+ QString name {};
+ auto id = uiob->qualifiedTypeNameId;
+ QStringRef prefix = uiob->qualifiedTypeNameId->name;
+ while (id) {
+ name += id->name.toString() + QLatin1Char('.');
+ id = id->next;
+ }
+ name.chop(1);
+ enterEnvironment(ScopeType::QMLScope, name);
+ if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component and QtObject, but they also have no interesting properties
+ return true;
+ importExportedNames(prefix, name);
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
+{
+ leaveEnvironment();
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
+{
+ QString name {};
+ auto id = uiod->qualifiedTypeNameId;
+ QStringRef prefix = uiod->qualifiedTypeNameId->name;
+ while (id) {
+ name += id->name.toString() + QLatin1Char('.');
+ id = id->next;
+ }
+ name.chop(1);
+ enterEnvironment(ScopeType::QMLScope, name);
+ if (name.isLower())
+ return false; // Ignore grouped properties for now
+ if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component
+ return true;
+ importExportedNames(prefix, name);
+ if (name.endsWith("Connections")) {
+ QString target;
+ auto member = uiod->initializer->members;
+ while (member) {
+ if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
+ auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member);
+ if (asBinding->qualifiedId->name == QLatin1String("target")) {
+ if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
+ auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression;
+ if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) {
+ target = idexpr->name.toString();
+ } else {
+ // more complex expressions are not supported
+ }
+ }
+ break;
+ }
+ }
+ member = member->next;
+ }
+ LanguageUtils::FakeMetaObject::ConstPtr metaObject {};
+ if (target.isEmpty()) {
+ // no target set, connection comes from parentF
+ ScopeTree* scope = m_currentScope;
+ do {
+ scope = scope->parentScope(); // TODO: rename method
+ } while (scope->scopeType() != ScopeType::QMLScope);
+ auto metaObject = m_exportedName2MetaObject[scope->name()];
+ } else {
+ // there was a target, check if we already can find it
+ auto metaObjectIt = m_qmlid2meta.find(target);
+ if (metaObjectIt != m_qmlid2meta.end()) {
+ metaObject = *metaObjectIt;
+ } else {
+ m_outstandingConnections.push_back({target, m_currentScope, uiod});
+ return false; // visit children later once target is known
+ }
+ }
+ m_currentScope->addMethodsFromMetaObject(metaObject);
+ }
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *)
+{
+ leaveEnvironment();
+}
+
+QDebug &operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << loc.startLine;
+ dbg.nospace() << ":";
+ dbg.nospace() << loc.startColumn;
+ return dbg.maybeSpace();
+}
diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h
new file mode 100644
index 0000000000..937194d142
--- /dev/null
+++ b/tools/qmllint/findunqualified.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef FINDUNQUALIFIED_H
+#define FINDUNQUALIFIED_H
+
+#include "qmljstypedescriptionreader.h"
+#include "qcoloroutput_p.h"
+
+#include <private/qqmljsastvisitor_p.h>
+#include <private/qqmljsast_p.h>
+
+#include <QScopedPointer>
+
+class ScopeTree;
+enum class ScopeType;
+
+class FindUnqualifiedIDVisitor : public QQmlJS::AST::Visitor {
+
+public:
+ explicit FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, const QString& code, const QString& fileName);
+ ~FindUnqualifiedIDVisitor() override;
+ bool check();
+
+private:
+ QScopedPointer<ScopeTree> m_rootScope;
+ ScopeTree *m_currentScope;
+ QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> m_exportedName2MetaObject;
+ QStringList m_qmltypeDirs;
+ const QString& m_code;
+ QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> m_qmlid2meta;
+ QString m_rootId;
+ QString m_filePath;
+ QSet<QPair<QString, QString>> m_alreadySeenImports;
+ ColorOutput m_colorOut;
+
+ struct OutstandingConnection {QString targetName; ScopeTree *scope; QQmlJS::AST::UiObjectDefinition *uiod;};
+
+ QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered
+
+ void enterEnvironment(ScopeType type, QString name);
+ void leaveEnvironment();
+ void importHelper(QString id, QString prefix, int major, int minor);
+ LanguageUtils::FakeMetaObject* localQmlFile2FakeMetaObject(QString filePath);
+
+
+ void importExportedNames(QStringRef prefix, QString name);
+
+ void throwRecursionDepthError() override;
+
+ // start block/scope handling
+ bool visit(QQmlJS::AST::UiProgram *ast) override;
+ void endVisit(QQmlJS::AST::UiProgram *ast) override;
+
+ bool visit(QQmlJS::AST::ClassExpression *ast) override;
+ void endVisit(QQmlJS::AST::ClassExpression *ast) override;
+
+ bool visit(QQmlJS::AST::ClassDeclaration *ast) override;
+ void endVisit(QQmlJS::AST::ClassDeclaration *ast) override;
+
+ bool visit(QQmlJS::AST::ForStatement *ast) override;
+ void endVisit(QQmlJS::AST::ForStatement *ast) override;
+
+ bool visit(QQmlJS::AST::ForEachStatement *ast) override;
+ void endVisit(QQmlJS::AST::ForEachStatement *ast) override;
+
+ bool visit(QQmlJS::AST::Block *ast) override;
+ void endVisit(QQmlJS::AST::Block *ast) override;
+
+ bool visit(QQmlJS::AST::CaseBlock *ast) override;
+ void endVisit(QQmlJS::AST::CaseBlock *ast) override;
+
+ bool visit(QQmlJS::AST::Catch *ast) override;
+ void endVisit(QQmlJS::AST::Catch *ast) override;
+
+ bool visit(QQmlJS::AST::WithStatement *withStatement) override;
+ void endVisit(QQmlJS::AST::WithStatement *ast) override;
+
+ void visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr);
+ bool visit(QQmlJS::AST::FunctionExpression *fexpr) override;
+ void endVisit(QQmlJS::AST::FunctionExpression *fexpr) override;
+
+ bool visit(QQmlJS::AST::FunctionDeclaration *fdecl) override;
+ void endVisit(QQmlJS::AST::FunctionDeclaration *fdecl) override;
+ /* --- end block handling --- */
+
+ bool visit(QQmlJS::AST::VariableDeclarationList *vdl) override;
+ bool visit(QQmlJS::AST::FormalParameterList *fpl) override;
+
+ bool visit(QQmlJS::AST::UiImport *import) override;
+ bool visit(QQmlJS::AST::UiEnumDeclaration *uied) override;
+ bool visit(QQmlJS::AST::UiObjectBinding *uiob) override;
+ void endVisit(QQmlJS::AST::UiObjectBinding *uiob) override;
+ bool visit(QQmlJS::AST::UiObjectDefinition *uiod) override;
+ void endVisit(QQmlJS::AST::UiObjectDefinition *) override;
+ bool visit(QQmlJS::AST::UiScriptBinding *uisb) override;
+ bool visit(QQmlJS::AST::UiPublicMember *uipm) override;
+
+ // expression handling
+ bool visit(QQmlJS::AST::IdentifierExpression *idexp) override;
+};
+
+
+#endif // FINDUNQUALIFIED_H
diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp
index bf0ceb54b7..235ec16c6e 100644
--- a/tools/qmllint/main.cpp
+++ b/tools/qmllint/main.cpp
@@ -34,12 +34,19 @@
#endif
#include <QCoreApplication>
-#include <private/qv4staticvalue_p.h>
+#ifndef QT_BOOTSTRAPPED
+#include <QLibraryInfo>
+#endif
+
#include <private/qqmljslexer_p.h>
#include <private/qqmljsparser_p.h>
#include <private/qqmljsengine_p.h>
+#include <private/qqmljsastvisitor_p.h>
+#include <private/qqmljsast_p.h>
+
+#include "findunqualified.h"
-static bool lint_file(const QString &filename, bool silent)
+static bool lint_file(const QString &filename, const bool silent, const bool warnUnqualied, QStringList const &qmltypeDirs)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
@@ -67,6 +74,13 @@ static bool lint_file(const QString &filename, bool silent)
}
}
+ if (success && !isJavaScript && warnUnqualied) {
+ auto root = parser.rootNode();
+ FindUnqualifiedIDVisitor v { qmltypeDirs, code, filename};
+ root->accept(&v);
+ success = v.check();
+ }
+
return success;
}
@@ -80,8 +94,20 @@ int main(int argv, char *argc[])
parser.setApplicationDescription(QLatin1String("QML syntax verifier"));
parser.addHelpOption();
parser.addVersionOption();
+
QCommandLineOption silentOption(QStringList() << "s" << "silent", QLatin1String("Don't output syntax errors"));
parser.addOption(silentOption);
+
+ QCommandLineOption checkUnqualified(QStringList() << "U" << "check-unqualified", QLatin1String("Warn about unqualified identifiers"));
+ parser.addOption(checkUnqualified);
+
+ QCommandLineOption qmltypesDirsOption(
+ QStringList() << "I"
+ << "qmldirs",
+ QLatin1String("Look for qmltypes files in specified directory"),
+ QLatin1String("directory"));
+ parser.addOption(qmltypesDirsOption);
+
parser.addPositionalArgument(QLatin1String("files"), QLatin1String("list of qml or js files to verify"));
parser.process(app);
@@ -92,8 +118,18 @@ int main(int argv, char *argc[])
}
bool silent = parser.isSet(silentOption);
+ bool warnUnqualified = parser.isSet(checkUnqualified);
+ // use host qml import path as a sane default if nothing else has been provided
+ QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption) ? parser.values(qmltypesDirsOption)
+#ifndef QT_BOOTSTRAPPED
+ : QStringList{QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)};
+#else
+ : QStringList{};
+#endif
#else
bool silent = false;
+ bool warnUnqualified = false;
+ QStringList qmltypeDirs {};
#endif
bool success = true;
#if QT_CONFIG(commandlineparser)
@@ -102,7 +138,7 @@ int main(int argv, char *argc[])
const auto arguments = app.arguments();
for (const QString &filename : arguments)
#endif
- success &= lint_file(filename, silent);
+ success &= lint_file(filename, silent, warnUnqualified, qmltypeDirs);
return success ? 0 : -1;
}
diff --git a/tools/qmllint/qcoloroutput.cpp b/tools/qmllint/qcoloroutput.cpp
new file mode 100644
index 0000000000..d2e723700a
--- /dev/null
+++ b/tools/qmllint/qcoloroutput.cpp
@@ -0,0 +1,342 @@
+/****************************************************************************
+**
+** 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 <QFile>
+#include <QHash>
+#include <QTextCodec>
+
+#ifndef Q_OS_WIN
+#include <unistd.h>
+#endif
+
+#include "qcoloroutput_p.h"
+
+class ColorOutputPrivate
+{
+public:
+ ColorOutputPrivate() : currentColorID(-1)
+
+ {
+ /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, performance
+ * is considered of lower priority.
+ */
+ m_out.open(stderr, QIODevice::WriteOnly | QIODevice::Unbuffered);
+
+ coloringEnabled = isColoringPossible();
+ }
+
+ ColorOutput::ColorMapping colorMapping;
+ int currentColorID;
+ bool coloringEnabled;
+
+ static const char *const foregrounds[];
+ static const char *const backgrounds[];
+
+ inline void write(const QString &msg)
+ {
+ m_out.write(msg.toLocal8Bit());
+ }
+
+ static QString escapeCode(const QString &in)
+ {
+ QString result;
+ result.append(QChar(0x1B));
+ result.append(QLatin1Char('['));
+ result.append(in);
+ result.append(QLatin1Char('m'));
+ return result;
+ }
+
+private:
+ QFile m_out;
+
+ /*!
+ Returns true if it's suitable to send colored output to \c stderr.
+ */
+ inline bool isColoringPossible() const
+ {
+# if defined(Q_OS_WIN)
+ /* Windows doesn't at all support ANSI escape codes, unless
+ * the user install a "device driver". See the Wikipedia links in the
+ * class documentation for details. */
+ return false;
+# else
+ /* We use QFile::handle() to get the file descriptor. It's a bit unsure
+ * whether it's 2 on all platforms and in all cases, so hopefully this layer
+ * of abstraction helps handle such cases. */
+ return isatty(m_out.handle());
+# endif
+ }
+};
+
+const char *const ColorOutputPrivate::foregrounds[] =
+{
+ "0;30",
+ "0;34",
+ "0;32",
+ "0;36",
+ "0;31",
+ "0;35",
+ "0;33",
+ "0;37",
+ "1;30",
+ "1;34",
+ "1;32",
+ "1;36",
+ "1;31",
+ "1;35",
+ "1;33",
+ "1;37"
+};
+
+const char *const ColorOutputPrivate::backgrounds[] =
+{
+ "0;40",
+ "0;44",
+ "0;42",
+ "0;46",
+ "0;41",
+ "0;45",
+ "0;43"
+};
+
+/*!
+ \class ColorOutput
+ \since 4.4
+ \nonreentrant
+ \brief Outputs colored messages to \c stderr.
+ \internal
+
+ ColorOutput is a convenience class for outputting messages to \c
+ stderr using color escape codes, as mandated in ECMA-48. ColorOutput
+ will only color output when it is detected to be suitable. For
+ instance, if \c stderr is detected to be attached to a file instead
+ of a TTY, no coloring will be done.
+
+ ColorOutput does its best attempt. but it is generally undefined
+ what coloring or effect the various coloring flags has. It depends
+ strongly on what terminal software that is being used.
+
+ When using `echo -e 'my escape sequence'`, \c{\033} works as an
+ initiator but not when printing from a C++ program, despite having
+ escaped the backslash. That's why we below use characters with
+ value 0x1B.
+
+ It can be convenient to subclass ColorOutput with a private scope,
+ such that the functions are directly available in the class using
+ it.
+
+ \section1 Usage
+
+ To output messages, call write() or writeUncolored(). write() takes
+ as second argument an integer, which ColorOutput uses as a lookup
+ key to find the color it should color the text in. The mapping from
+ keys to colors is done using insertMapping(). Typically this is used
+ by having enums for the various kinds of messages, which
+ subsequently are registered.
+
+ \code
+ enum MyMessage
+ {
+ Error,
+ Important
+ };
+
+ ColorOutput output;
+ output.insertMapping(Error, ColorOutput::RedForeground);
+ output.insertMapping(Import, ColorOutput::BlueForeground);
+
+ output.write("This is important", Important);
+ output.write("Jack, I'm only the selected official!", Error);
+ \endcode
+
+ \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors},
+ {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade},
+ {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International},
+ {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code},
+ {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala}
+ */
+
+/*!
+ \enum ColorOutput::ColorCodeComponent
+ \value BlackForeground
+ \value BlueForeground
+ \value GreenForeground
+ \value CyanForeground
+ \value RedForeground
+ \value PurpleForeground
+ \value BrownForeground
+ \value LightGrayForeground
+ \value DarkGrayForeground
+ \value LightBlueForeground
+ \value LightGreenForeground
+ \value LightCyanForeground
+ \value LightRedForeground
+ \value LightPurpleForeground
+ \value YellowForeground
+ \value WhiteForeground
+ \value BlackBackground
+ \value BlueBackground
+ \value GreenBackground
+ \value CyanBackground
+ \value RedBackground
+ \value PurpleBackground
+ \value BrownBackground
+
+ \value DefaultColor ColorOutput performs no coloring. This typically
+ means black on white or white on black, depending
+ on the settings of the user's terminal.
+ */
+
+/*!
+ Sets the color mapping to be \a cMapping.
+
+ Negative values are disallowed.
+
+ \sa colorMapping(), insertMapping()
+ */
+void ColorOutput::setColorMapping(const ColorMapping &cMapping)
+{
+ d->colorMapping = cMapping;
+}
+
+/*!
+ Returns the color mappings in use.
+
+ \sa setColorMapping(), insertMapping()
+ */
+ColorOutput::ColorMapping ColorOutput::colorMapping() const
+{
+ return d->colorMapping;
+}
+
+/*!
+ Constructs a ColorOutput instance, ready for use.
+ */
+ColorOutput::ColorOutput() : d(new ColorOutputPrivate())
+{
+}
+
+ColorOutput::~ColorOutput() = default; // must be here so that QScopedPointer has access to the complete type
+
+/*!
+ Sends \a message to \c stderr, using the color looked up in colorMapping() using \a colorID.
+
+ If \a color isn't available in colorMapping(), result and behavior is undefined.
+
+ If \a colorID is 0, which is the default value, the previously used coloring is used. ColorOutput
+ is initialized to not color at all.
+
+ If \a message is empty, effects are undefined.
+
+ \a message will be printed as is. For instance, no line endings will be inserted.
+ */
+void ColorOutput::write(const QString &message, int colorID)
+{
+ d->write(colorify(message, colorID));
+}
+
+/*!
+ Writes \a message to \c stderr as if for instance
+ QTextStream would have been used, and adds a line ending at the end.
+
+ This function can be practical to use such that one can use ColorOutput for all forms of writing.
+ */
+void ColorOutput::writeUncolored(const QString &message)
+{
+ d->write(message + QLatin1Char('\n'));
+}
+
+/*!
+ Treats \a message and \a colorID identically to write(), but instead of writing
+ \a message to \c stderr, it is prepared for being written to \c stderr, but is then
+ returned.
+
+ This is useful when the colored string is inserted into a translated string(dividing
+ the string into several small strings prevents proper translation).
+ */
+QString ColorOutput::colorify(const QString &message, int colorID) const
+{
+ Q_ASSERT_X(colorID == -1 || d->colorMapping.contains(colorID), Q_FUNC_INFO,
+ qPrintable(QString::fromLatin1("There is no color registered by id %1").arg(colorID)));
+ Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO, "It makes no sense to attempt to print an empty string.");
+
+ if (colorID != -1)
+ d->currentColorID = colorID;
+
+ if (d->coloringEnabled && colorID != -1)
+ {
+ const int color(d->colorMapping.value(colorID));
+
+ /* If DefaultColor is set, we don't want to color it. */
+ if (color & DefaultColor)
+ return message;
+
+ const int foregroundCode = (int(color) & ForegroundMask) >> ForegroundShift;
+ const int backgroundCode = (int(color) & BackgroundMask) >> BackgroundShift;
+ QString finalMessage;
+ bool closureNeeded = false;
+
+ if (foregroundCode)
+ {
+ finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1])));
+ closureNeeded = true;
+ }
+
+ if (backgroundCode)
+ {
+ finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1])));
+ closureNeeded = true;
+ }
+
+ finalMessage.append(message);
+
+ if (closureNeeded)
+ {
+ finalMessage.append(QChar(0x1B));
+ finalMessage.append(QLatin1String("[0m"));
+ }
+
+ return finalMessage;
+ }
+ else
+ return message;
+}
+
+/*!
+ Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance.
+
+ This is a convenience function for creating a ColorOutput::ColorMapping instance and
+ calling setColorMapping().
+
+ \sa colorMapping(), setColorMapping()
+ */
+void ColorOutput::insertColorMapping(int colorID, const ColorCode colorCode)
+{
+ d->colorMapping.insert(colorID, colorCode);
+}
diff --git a/tools/qmllint/qcoloroutput_p.h b/tools/qmllint/qcoloroutput_p.h
new file mode 100644
index 0000000000..710bf5db74
--- /dev/null
+++ b/tools/qmllint/qcoloroutput_p.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QCOLOROUTPUT_P_H
+#define QCOLOROUTPUT_P_H
+
+#include <QtCore/QtGlobal>
+#include <QtCore/QHash>
+#include <QScopedPointer>
+
+class ColorOutputPrivate;
+
+class ColorOutput
+{
+ enum
+ {
+ ForegroundShift = 10,
+ BackgroundShift = 20,
+ SpecialShift = 20,
+ ForegroundMask = 0x1f << ForegroundShift,
+ BackgroundMask = 0x7 << BackgroundShift
+ };
+
+public:
+ enum ColorCodeComponent
+ {
+ BlackForeground = 1 << ForegroundShift,
+ BlueForeground = 2 << ForegroundShift,
+ GreenForeground = 3 << ForegroundShift,
+ CyanForeground = 4 << ForegroundShift,
+ RedForeground = 5 << ForegroundShift,
+ PurpleForeground = 6 << ForegroundShift,
+ BrownForeground = 7 << ForegroundShift,
+ LightGrayForeground = 8 << ForegroundShift,
+ DarkGrayForeground = 9 << ForegroundShift,
+ LightBlueForeground = 10 << ForegroundShift,
+ LightGreenForeground = 11 << ForegroundShift,
+ LightCyanForeground = 12 << ForegroundShift,
+ LightRedForeground = 13 << ForegroundShift,
+ LightPurpleForeground = 14 << ForegroundShift,
+ YellowForeground = 15 << ForegroundShift,
+ WhiteForeground = 16 << ForegroundShift,
+
+ BlackBackground = 1 << BackgroundShift,
+ BlueBackground = 2 << BackgroundShift,
+ GreenBackground = 3 << BackgroundShift,
+ CyanBackground = 4 << BackgroundShift,
+ RedBackground = 5 << BackgroundShift,
+ PurpleBackground = 6 << BackgroundShift,
+ BrownBackground = 7 << BackgroundShift,
+ DefaultColor = 1 << SpecialShift
+ };
+
+ typedef QFlags<ColorCodeComponent> ColorCode;
+ typedef QHash<int, ColorCode> ColorMapping;
+
+ ColorOutput();
+ ~ColorOutput();
+
+ void setColorMapping(const ColorMapping &cMapping);
+ ColorMapping colorMapping() const;
+ void insertColorMapping(int colorID, const ColorCode colorCode);
+
+ void writeUncolored(const QString &message);
+ void write(const QString &message, int color = -1);
+ QString colorify(const QString &message, int color = -1) const;
+
+private:
+ QScopedPointer<ColorOutputPrivate> d;
+ Q_DISABLE_COPY(ColorOutput)
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(ColorOutput::ColorCode)
+
+#endif
diff --git a/tools/qmllint/qmljstypedescriptionreader.cpp b/tools/qmllint/qmljstypedescriptionreader.cpp
new file mode 100644
index 0000000000..542cdf99eb
--- /dev/null
+++ b/tools/qmllint/qmljstypedescriptionreader.cpp
@@ -0,0 +1,704 @@
+/****************************************************************************
+**
+** 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 "qmljstypedescriptionreader.h"
+
+#include <private/qqmljsparser_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsengine_p.h>
+
+#include <QDir>
+
+#define QTC_ASSERT_STRINGIFY_HELPER(x) #x
+#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x)
+#define QTC_ASSERT_STRING(cond) qDebug() << (\
+ "\"" cond"\" in file " __FILE__ ", line " QTC_ASSERT_STRINGIFY(__LINE__))
+#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0)
+
+using namespace QQmlJS;
+using namespace QQmlJS::AST;
+using namespace LanguageUtils;
+
+QString toString(const AST::UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
+{
+ QString result;
+
+ for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
+ if (iter != qualifiedId)
+ result += delimiter;
+
+ result += iter->name;
+ }
+
+ return result;
+}
+
+TypeDescriptionReader::TypeDescriptionReader(const QString &fileName, const QString &data)
+ : _fileName (fileName), _source(data), _objects(0)
+{
+}
+
+TypeDescriptionReader::~TypeDescriptionReader()
+{
+}
+
+bool TypeDescriptionReader::operator()(
+ QHash<QString, FakeMetaObject::ConstPtr> *objects,
+ QList<ModuleApiInfo> *moduleApis,
+ QStringList *dependencies)
+{
+ Engine engine;
+
+ Lexer lexer(&engine);
+ Parser parser(&engine);
+
+ lexer.setCode(_source, /*line = */ 1, /*qmlMode = */true);
+
+ if (!parser.parse()) {
+ _errorMessage = QString::fromLatin1("%1:%2: %3").arg(
+ QString::number(parser.errorLineNumber()),
+ QString::number(parser.errorColumnNumber()),
+ parser.errorMessage());
+ return false;
+ }
+
+ _objects = objects;
+ _moduleApis = moduleApis;
+ _dependencies = dependencies;
+ readDocument(parser.ast());
+
+ return _errorMessage.isEmpty();
+}
+
+QString TypeDescriptionReader::errorMessage() const
+{
+ return _errorMessage;
+}
+
+QString TypeDescriptionReader::warningMessage() const
+{
+ return _warningMessage;
+}
+
+void TypeDescriptionReader::readDocument(UiProgram *ast)
+{
+ if (!ast) {
+ addError(SourceLocation(), tr("Could not parse document."));
+ return;
+ }
+
+ if (!ast->headers || ast->headers->next || !AST::cast<AST::UiImport *>(ast->headers->headerItem)) {
+ addError(SourceLocation(), tr("Expected a single import."));
+ return;
+ }
+
+ UiImport *import = AST::cast<AST::UiImport *>(ast->headers->headerItem);
+ if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) {
+ addError(import->importToken, tr("Expected import of QtQuick.tooling."));
+ return;
+ }
+
+ ComponentVersion version;
+ const QString versionString = _source.mid(import->versionToken.offset, import->versionToken.length);
+ const int dotIdx = versionString.indexOf(QLatin1Char('.'));
+ if (dotIdx != -1) {
+ version = ComponentVersion(versionString.leftRef(dotIdx).toInt(),
+ versionString.midRef(dotIdx + 1).toInt());
+ }
+ if (version.majorVersion() != 1) {
+ addError(import->versionToken, tr("Major version different from 1 not supported."));
+ return;
+ }
+
+ if (!ast->members || !ast->members->member || ast->members->next) {
+ addError(SourceLocation(), tr("Expected document to contain a single object definition."));
+ return;
+ }
+
+ UiObjectDefinition *module = AST::cast<UiObjectDefinition *>(ast->members->member);
+ if (!module) {
+ addError(SourceLocation(), tr("Expected document to contain a single object definition."));
+ return;
+ }
+
+ if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) {
+ addError(SourceLocation(), tr("Expected document to contain a Module {} member."));
+ return;
+ }
+
+ readModule(module);
+}
+
+void TypeDescriptionReader::readModule(UiObjectDefinition *ast)
+{
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member);
+
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) {
+ readDependencies(script);
+ continue;
+ }
+
+ QString typeName;
+ if (component)
+ typeName = toString(component->qualifiedTypeNameId);
+
+ if (!component || (typeName != QLatin1String("Component") && typeName != QLatin1String("ModuleApi"))) {
+ continue;
+ }
+
+ if (typeName == QLatin1String("Component"))
+ readComponent(component);
+ else if (typeName == QLatin1String("ModuleApi"))
+ readModuleApi(component);
+ }
+}
+
+void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &message)
+{
+ _errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
+ QDir::toNativeSeparators(_fileName),
+ QString::number(loc.startLine),
+ QString::number(loc.startColumn),
+ message);
+}
+
+void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message)
+{
+ _warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
+ QDir::toNativeSeparators(_fileName),
+ QString::number(loc.startLine),
+ QString::number(loc.startColumn),
+ message);
+}
+
+void TypeDescriptionReader::readDependencies(UiScriptBinding *ast)
+{
+ ExpressionStatement *stmt = AST::cast<ExpressionStatement*>(ast->statement);
+ if (!stmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions"));
+ return;
+ }
+ ArrayPattern *exp = AST::cast<ArrayPattern *>(stmt->expression);
+ if (!exp) {
+ addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions"));
+ return;
+ }
+ for (PatternElementList *l = exp->elements; l; l = l->next) {
+ //StringLiteral *str = AST::cast<StringLiteral *>(l->element->initializer);
+ StringLiteral *str = AST::cast<StringLiteral *>(l->element->initializer);
+ *_dependencies << str->value.toString();
+ }
+}
+
+void TypeDescriptionReader::readComponent(UiObjectDefinition *ast)
+{
+ FakeMetaObject::Ptr fmo(new FakeMetaObject);
+
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member);
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ if (component) {
+ QString name = toString(component->qualifiedTypeNameId);
+ if (name == QLatin1String("Property"))
+ readProperty(component, fmo);
+ else if (name == QLatin1String("Method") || name == QLatin1String("Signal"))
+ readSignalOrMethod(component, name == QLatin1String("Method"), fmo);
+ else if (name == QLatin1String("Enum"))
+ readEnum(component, fmo);
+ else
+ addWarning(component->firstSourceLocation(),
+ tr("Expected only Property, Method, Signal and Enum object definitions, not \"%1\".")
+ .arg(name));
+ } else if (script) {
+ QString name = toString(script->qualifiedId);
+ if (name == QLatin1String("name")) {
+ fmo->setClassName(readStringBinding(script));
+ } else if (name == QLatin1String("prototype")) {
+ fmo->setSuperclassName(readStringBinding(script));
+ } else if (name == QLatin1String("defaultProperty")) {
+ fmo->setDefaultPropertyName(readStringBinding(script));
+ } else if (name == QLatin1String("exports")) {
+ readExports(script, fmo);
+ } else if (name == QLatin1String("exportMetaObjectRevisions")) {
+ readMetaObjectRevisions(script, fmo);
+ } else if (name == QLatin1String("attachedType")) {
+ fmo->setAttachedTypeName(readStringBinding(script));
+ } else if (name == QLatin1String("isSingleton")) {
+ fmo->setIsSingleton(readBoolBinding(script));
+ } else if (name == QLatin1String("isCreatable")) {
+ fmo->setIsCreatable(readBoolBinding(script));
+ } else if (name == QLatin1String("isComposite")) {
+ fmo->setIsComposite(readBoolBinding(script));
+ } else {
+ addWarning(script->firstSourceLocation(),
+ tr("Expected only name, prototype, defaultProperty, attachedType, exports, "
+ "isSingleton, isCreatable, isComposite and exportMetaObjectRevisions "
+ "script bindings, not \"%1\".").arg(name));
+ }
+ } else {
+ addWarning(member->firstSourceLocation(), tr("Expected only script bindings and object definitions."));
+ }
+ }
+
+ if (fmo->className().isEmpty()) {
+ addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding."));
+ return;
+ }
+
+ // ### add implicit export into the package of c++ types
+ fmo->addExport(fmo->className(), QStringLiteral("<cpp>"), ComponentVersion());
+ fmo->updateFingerprint();
+ _objects->insert(fmo->className(), fmo);
+}
+
+void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast)
+{
+ ModuleApiInfo apiInfo;
+
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+
+ if (script) {
+ const QString name = toString(script->qualifiedId);
+ if (name == QLatin1String("uri")) {
+ apiInfo.uri = readStringBinding(script);
+ } else if (name == QLatin1String("version")) {
+ apiInfo.version = readNumericVersionBinding(script);
+ } else if (name == QLatin1String("name")) {
+ apiInfo.cppName = readStringBinding(script);
+ } else {
+ addWarning(script->firstSourceLocation(),
+ tr("Expected only uri, version and name script bindings."));
+ }
+ } else {
+ addWarning(member->firstSourceLocation(), tr("Expected only script bindings."));
+ }
+ }
+
+ if (!apiInfo.version.isValid()) {
+ addError(ast->firstSourceLocation(), tr("ModuleApi definition has no or invalid version binding."));
+ return;
+ }
+
+ if (_moduleApis)
+ _moduleApis->append(apiInfo);
+}
+
+void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, FakeMetaObject::Ptr fmo)
+{
+ FakeMetaMethod fmm;
+ // ### confusion between Method and Slot. Method should be removed.
+ if (isMethod)
+ fmm.setMethodType(FakeMetaMethod::Slot);
+ else
+ fmm.setMethodType(FakeMetaMethod::Signal);
+
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member);
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ if (component) {
+ QString name = toString(component->qualifiedTypeNameId);
+ if (name == QLatin1String("Parameter"))
+ readParameter(component, &fmm);
+ else
+ addWarning(component->firstSourceLocation(), tr("Expected only Parameter object definitions."));
+ } else if (script) {
+ QString name = toString(script->qualifiedId);
+ if (name == QLatin1String("name"))
+ fmm.setMethodName(readStringBinding(script));
+ else if (name == QLatin1String("type"))
+ fmm.setReturnType(readStringBinding(script));
+ else if (name == QLatin1String("revision"))
+ fmm.setRevision(readIntBinding(script));
+ else
+ addWarning(script->firstSourceLocation(), tr("Expected only name and type script bindings."));
+
+ } else {
+ addWarning(member->firstSourceLocation(), tr("Expected only script bindings and object definitions."));
+ }
+ }
+
+ if (fmm.methodName().isEmpty()) {
+ addError(ast->firstSourceLocation(), tr("Method or signal is missing a name script binding."));
+ return;
+ }
+
+ fmo->addMethod(fmm);
+}
+
+void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, FakeMetaObject::Ptr fmo)
+{
+ QString name;
+ QString type;
+ bool isPointer = false;
+ bool isReadonly = false;
+ bool isList = false;
+ int revision = 0;
+
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ if (!script) {
+ addWarning(member->firstSourceLocation(), tr("Expected script binding."));
+ continue;
+ }
+
+ QString id = toString(script->qualifiedId);
+ if (id == QLatin1String("name"))
+ name = readStringBinding(script);
+ else if (id == QLatin1String("type"))
+ type = readStringBinding(script);
+ else if (id == QLatin1String("isPointer"))
+ isPointer = readBoolBinding(script);
+ else if (id == QLatin1String("isReadonly"))
+ isReadonly = readBoolBinding(script);
+ else if (id == QLatin1String("isList"))
+ isList = readBoolBinding(script);
+ else if (id == QLatin1String("revision"))
+ revision = readIntBinding(script);
+ else
+ addWarning(script->firstSourceLocation(), tr("Expected only type, name, revision, isPointer, isReadonly and isList script bindings."));
+ }
+
+ if (name.isEmpty() || type.isEmpty()) {
+ addError(ast->firstSourceLocation(), tr("Property object is missing a name or type script binding."));
+ return;
+ }
+
+ fmo->addProperty(FakeMetaProperty(name, type, isList, !isReadonly, isPointer, revision));
+}
+
+void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, FakeMetaObject::Ptr fmo)
+{
+ FakeMetaEnum fme;
+
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ if (!script) {
+ addWarning(member->firstSourceLocation(), tr("Expected script binding."));
+ continue;
+ }
+
+ QString name = toString(script->qualifiedId);
+ if (name == QLatin1String("name"))
+ fme.setName(readStringBinding(script));
+ else if (name == QLatin1String("values"))
+ readEnumValues(script, &fme);
+ else
+ addWarning(script->firstSourceLocation(), tr("Expected only name and values script bindings."));
+ }
+
+ fmo->addEnum(fme);
+}
+
+void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, FakeMetaMethod *fmm)
+{
+ QString name;
+ QString type;
+
+ for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
+ UiObjectMember *member = it->member;
+ UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ if (!script) {
+ addWarning(member->firstSourceLocation(), tr("Expected script binding."));
+ continue;
+ }
+
+ const QString id = toString(script->qualifiedId);
+ if (id == QLatin1String("name")) {
+ name = readStringBinding(script);
+ } else if (id == QLatin1String("type")) {
+ type = readStringBinding(script);
+ } else if (id == QLatin1String("isPointer")) {
+ // ### unhandled
+ } else if (id == QLatin1String("isReadonly")) {
+ // ### unhandled
+ } else if (id == QLatin1String("isList")) {
+ // ### unhandled
+ } else {
+ addWarning(script->firstSourceLocation(), tr("Expected only name and type script bindings."));
+ }
+ }
+
+ fmm->addParameter(name, type);
+}
+
+QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
+{
+ QTC_ASSERT(ast, return QString());
+
+ if (!ast->statement) {
+ addError(ast->colonToken, tr("Expected string after colon."));
+ return QString();
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected string after colon."));
+ return QString();
+ }
+
+ StringLiteral *stringLit = AST::cast<StringLiteral *>(expStmt->expression);
+ if (!stringLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected string after colon."));
+ return QString();
+ }
+
+ return stringLit->value.toString();
+}
+
+bool TypeDescriptionReader::readBoolBinding(AST::UiScriptBinding *ast)
+{
+ QTC_ASSERT(ast, return false);
+
+ if (!ast->statement) {
+ addError(ast->colonToken, tr("Expected boolean after colon."));
+ return false;
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon."));
+ return false;
+ }
+
+ TrueLiteral *trueLit = AST::cast<TrueLiteral *>(expStmt->expression);
+ FalseLiteral *falseLit = AST::cast<FalseLiteral *>(expStmt->expression);
+ if (!trueLit && !falseLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon."));
+ return false;
+ }
+
+ return trueLit;
+}
+
+double TypeDescriptionReader::readNumericBinding(AST::UiScriptBinding *ast)
+{
+ QTC_ASSERT(ast, return qQNaN());
+
+ if (!ast->statement) {
+ addError(ast->colonToken, tr("Expected numeric literal after colon."));
+ return 0;
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon."));
+ return 0;
+ }
+
+ NumericLiteral *numericLit = AST::cast<NumericLiteral *>(expStmt->expression);
+ if (!numericLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
+ return 0;
+ }
+
+ return numericLit->value;
+}
+
+ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast)
+{
+ ComponentVersion invalidVersion;
+
+ if (!ast || !ast->statement) {
+ addError((ast ? ast->colonToken : SourceLocation()), tr("Expected numeric literal after colon."));
+ return invalidVersion;
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon."));
+ return invalidVersion;
+ }
+
+ NumericLiteral *numericLit = AST::cast<NumericLiteral *>(expStmt->expression);
+ if (!numericLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
+ return invalidVersion;
+ }
+
+ return ComponentVersion(_source.mid(numericLit->literalToken.begin(), numericLit->literalToken.length));
+}
+
+int TypeDescriptionReader::readIntBinding(AST::UiScriptBinding *ast)
+{
+ double v = readNumericBinding(ast);
+ int i = static_cast<int>(v);
+
+ if (i != v) {
+ addError(ast->firstSourceLocation(), tr("Expected integer after colon."));
+ return 0;
+ }
+
+ return i;
+}
+
+void TypeDescriptionReader::readExports(UiScriptBinding *ast, FakeMetaObject::Ptr fmo)
+{
+ QTC_ASSERT(ast, return);
+
+ if (!ast->statement) {
+ addError(ast->colonToken, tr("Expected array of strings after colon."));
+ return;
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected array of strings after colon."));
+ return;
+ }
+
+ ArrayPattern *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression);
+ if (!arrayLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon."));
+ return;
+ }
+
+ for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
+ StringLiteral *stringLit = AST::cast<StringLiteral *>(it->element->initializer);
+ if (!stringLit) {
+ addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only string literal members."));
+ return;
+ }
+ QString exp = stringLit->value.toString();
+ int slashIdx = exp.indexOf(QLatin1Char('/'));
+ int spaceIdx = exp.indexOf(QLatin1Char(' '));
+ ComponentVersion version(exp.mid(spaceIdx + 1));
+
+ if (spaceIdx == -1 || !version.isValid()) {
+ addError(stringLit->firstSourceLocation(), tr("Expected string literal to contain 'Package/Name major.minor' or 'Name major.minor'."));
+ continue;
+ }
+ QString package;
+ if (slashIdx != -1)
+ package = exp.left(slashIdx);
+ QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1));
+
+ // ### relocatable exports where package is empty?
+ fmo->addExport(name, package, version);
+ }
+}
+
+void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, FakeMetaObject::Ptr fmo)
+{
+ QTC_ASSERT(ast, return);
+
+ if (!ast->statement) {
+ addError(ast->colonToken, tr("Expected array of numbers after colon."));
+ return;
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected array of numbers after colon."));
+ return;
+ }
+
+ ArrayPattern *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression);
+ if (!arrayLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon."));
+ return;
+ }
+
+ int exportIndex = 0;
+ const int exportCount = fmo->exports().size();
+ for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) {
+ NumericLiteral *numberLit = cast<NumericLiteral *>(it->element->initializer);
+ if (!numberLit) {
+ addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only number literal members."));
+ return;
+ }
+
+ if (exportIndex >= exportCount) {
+ addError(numberLit->firstSourceLocation(), tr("Meta object revision without matching export."));
+ return;
+ }
+
+ const double v = numberLit->value;
+ const int metaObjectRevision = static_cast<int>(v);
+ if (metaObjectRevision != v) {
+ addError(numberLit->firstSourceLocation(), tr("Expected integer."));
+ return;
+ }
+
+ fmo->setExportMetaObjectRevision(exportIndex, metaObjectRevision);
+ }
+}
+
+void TypeDescriptionReader::readEnumValues(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaEnum *fme)
+{
+ if (!ast)
+ return;
+ if (!ast->statement) {
+ addError(ast->colonToken, tr("Expected object literal after colon."));
+ return;
+ }
+
+ ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ if (!expStmt) {
+ addError(ast->statement->firstSourceLocation(), tr("Expected object literal after colon."));
+ return;
+ }
+
+ ObjectPattern *objectLit = AST::cast<ObjectPattern *>(expStmt->expression);
+ if (!objectLit) {
+ addError(expStmt->firstSourceLocation(), tr("Expected object literal after colon."));
+ return;
+ }
+
+ for (PatternPropertyList *it = objectLit->properties; it; it = it->next) {
+ PatternProperty *assignement = AST::cast<PatternProperty *>(it->property);
+ if (assignement) {
+ StringLiteralPropertyName *propName = AST::cast<StringLiteralPropertyName *>(assignement->name);
+ NumericLiteral *value = AST::cast<NumericLiteral *>(assignement->initializer);
+ UnaryMinusExpression *minus = AST::cast<UnaryMinusExpression *>(assignement->initializer);
+ if (minus)
+ value = AST::cast<NumericLiteral *>(minus->expression);
+ if (!propName || !value) {
+ addError(objectLit->firstSourceLocation(), tr("Expected object literal to contain only 'string: number' elements."));
+ continue;
+ }
+
+ double v = value->value;
+ if (minus)
+ v = -v;
+ fme->addKey(propName->id.toString(), v);
+ continue;
+ }
+ PatternPropertyList *getterSetter = AST::cast<PatternPropertyList *>(it->next);
+ if (getterSetter)
+ addError(objectLit->firstSourceLocation(), tr("Enum should not contain getter and setters, but only 'string: number' elements."));
+ }
+}
diff --git a/tools/qmllint/qmljstypedescriptionreader.h b/tools/qmllint/qmljstypedescriptionreader.h
new file mode 100644
index 0000000000..df215af8d2
--- /dev/null
+++ b/tools/qmllint/qmljstypedescriptionreader.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLJSTYPEDESCRIPTIONREADER_H
+#define QMLJSTYPEDESCRIPTIONREADER_H
+
+#include <private/qqmljsastfwd_p.h>
+#include "fakemetaobject.h"
+
+// for Q_DECLARE_TR_FUNCTIONS
+#include <QCoreApplication>
+
+QT_BEGIN_NAMESPACE
+class QIODevice;
+class QBuffer;
+
+namespace QQmlJS {
+
+class ModuleApiInfo
+{
+public:
+ QString uri;
+ LanguageUtils::ComponentVersion version;
+ QString cppName;
+};
+
+
+class TypeDescriptionReader
+{
+ Q_DECLARE_TR_FUNCTIONS(QQmlJS::TypeDescriptionReader)
+
+public:
+ explicit TypeDescriptionReader(const QString &fileName, const QString &data);
+ ~TypeDescriptionReader();
+
+ bool operator()(
+ QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> *objects,
+ QList<ModuleApiInfo> *moduleApis,
+ QStringList *dependencies);
+ QString errorMessage() const;
+ QString warningMessage() const;
+
+private:
+ void readDocument(AST::UiProgram *ast);
+ void readModule(AST::UiObjectDefinition *ast);
+ void readDependencies(AST::UiScriptBinding *ast);
+ void readComponent(AST::UiObjectDefinition *ast);
+ void readModuleApi(AST::UiObjectDefinition *ast);
+ void readSignalOrMethod(AST::UiObjectDefinition *ast, bool isMethod, LanguageUtils::FakeMetaObject::Ptr fmo);
+ void readProperty(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
+ void readEnum(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
+ void readParameter(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaMethod *fmm);
+
+ QString readStringBinding(AST::UiScriptBinding *ast);
+ bool readBoolBinding(AST::UiScriptBinding *ast);
+ double readNumericBinding(AST::UiScriptBinding *ast);
+ LanguageUtils::ComponentVersion readNumericVersionBinding(AST::UiScriptBinding *ast);
+ int readIntBinding(AST::UiScriptBinding *ast);
+ void readExports(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
+ void readMetaObjectRevisions(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
+ void readEnumValues(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaEnum *fme);
+
+ void addError(const AST::SourceLocation &loc, const QString &message);
+ void addWarning(const AST::SourceLocation &loc, const QString &message);
+
+ QString _fileName;
+ QString _source;
+ QString _errorMessage;
+ QString _warningMessage;
+ QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> *_objects;
+ QList<ModuleApiInfo> *_moduleApis = nullptr;
+ QStringList *_dependencies = nullptr;
+};
+
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#endif // QMLJSTYPEDESCRIPTIONREADER_H
diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro
index 91ab2f8afc..76363a7cd8 100644
--- a/tools/qmllint/qmllint.pro
+++ b/tools/qmllint/qmllint.pro
@@ -2,8 +2,22 @@ option(host_build)
QT = core qmldevtools-private
-SOURCES += main.cpp
+SOURCES += main.cpp \
+ componentversion.cpp \
+ fakemetaobject.cpp \
+ findunqualified.cpp \
+ qmljstypedescriptionreader.cpp \
+ qcoloroutput.cpp \
+ scopetree.cpp
QMAKE_TARGET_DESCRIPTION = QML Syntax Verifier
load(qt_tool)
+
+HEADERS += \
+ componentversion.h \
+ fakemetaobject.h \
+ findunqualified.h \
+ qmljstypedescriptionreader.h \
+ qcoloroutput_p.h \
+ scopetree.h
diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp
new file mode 100644
index 0000000000..b064117dd4
--- /dev/null
+++ b/tools/qmllint/scopetree.cpp
@@ -0,0 +1,255 @@
+/****************************************************************************
+**
+** 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_p.h"
+
+#include <QQueue>
+
+ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope)
+ : m_parentScope(parentScope), m_name(name), m_scopeType(type) {}
+
+ScopeTree *ScopeTree::createNewChildScope(ScopeType type, QString name) {
+ Q_ASSERT(type != ScopeType::QMLScope|| !m_parentScope || m_parentScope->m_scopeType == ScopeType::QMLScope || m_parentScope->m_name == "global");
+ auto childScope = new ScopeTree{type, name, this};
+ m_childScopes.push_back(childScope);
+ return childScope;
+}
+
+ScopeTree *ScopeTree::parentScope() {
+ return m_parentScope;
+}
+
+void ScopeTree::insertJSIdentifier(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_currentScopeJSIdentifiers.insert(id);
+ } else {
+ m_currentScopeJSIdentifiers.insert(id);
+ }
+}
+
+void ScopeTree::insertQMLIdentifier(QString id)
+{
+ Q_ASSERT(m_scopeType == ScopeType::QMLScope);
+ m_currentScopeQMLIdentifiers.insert(id);
+}
+
+void ScopeTree::insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc)
+{
+ Q_ASSERT(m_scopeType == ScopeType::QMLScope);
+ m_injectedSignalIdentifiers.insert(id, {method, loc});
+}
+
+void ScopeTree::insertPropertyIdentifier(QString id)
+{
+ this->insertQMLIdentifier(id);
+ LanguageUtils::FakeMetaMethod method( id + QLatin1String("Changed"), "void");
+ this->addMethod(method);
+}
+
+bool ScopeTree::isIdInCurrentScope(const QString &id) const
+{
+ return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id);
+}
+
+void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair<QString, QQmlJS::AST::SourceLocation> &id_loc_pair) {
+ if (!isIdInCurrentScope(id_loc_pair.first)) {
+ m_accessedIdentifiers.push_back(id_loc_pair);
+ }
+}
+
+bool ScopeTree::isVisualRootScope() const
+{
+ return m_parentScope && m_parentScope->m_parentScope && m_parentScope->m_parentScope->m_parentScope == nullptr;
+}
+
+QString ScopeTree::name() const
+{
+ return m_name;
+}
+
+struct IssueLocationWithContext
+{
+ IssueLocationWithContext(const QString& code, QQmlJS::AST::SourceLocation location) {
+ int before = std::max(0,code.lastIndexOf('\n', location.offset));
+ beforeText = code.midRef(before+1, location.offset - (before+1) );
+ issueText = code.midRef(location.offset, location.length);
+ int after = code.indexOf('\n', location.offset + location.length);
+ afterText = code.midRef(location.offset+location.length, after - (location.offset+location.length));
+ }
+
+ QStringRef beforeText;
+ QStringRef issueText;
+ QStringRef afterText;
+};
+
+bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> &qmlIDs, 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 (auto idLocationPair : currentScope->m_accessedIdentifiers) {
+ if (qmlIDs.contains(idLocationPair.first))
+ continue;
+ if (currentScope->isIdInCurrentScope(idLocationPair.first)) {
+ continue;
+ }
+ noUnqualifiedIdentifier = false;
+ colorOut.write("Warning: ", Warning);
+ auto location = idLocationPair.second;
+ colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, location.startColumn), Normal);
+ 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);
+ // root(JS) --> program(qml) --> (first element)
+ if (root->m_childScopes[0]->m_childScopes[0]->m_currentScopeQMLIdentifiers.contains(idLocationPair.first)) {
+ ScopeTree *parentScope = currentScope->m_parentScope;
+ while (parentScope && parentScope->scopeType() != ScopeType::QMLScope) {
+ parentScope = parentScope->m_parentScope;
+ }
+ bool directChild = parentScope && parentScope->isVisualRootScope();
+ colorOut.write("Note: ", Info);
+ colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal );
+ if (directChild) {
+ colorOut.write(QLatin1String(" As the current element is a direct child of the root element,\n you can qualify the access with parent to avoid this warning:\n"), Normal);
+ } else {
+ 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"));
+ }
+ }
+ colorOut.write(issueLocationWithContext.beforeText.toString(), Normal);
+ colorOut.write( (directChild ? QLatin1String("parent") : rootId) + QLatin1Char('.'), Hint);
+ colorOut.write(issueLocationWithContext.issueText.toString(), Normal);
+ colorOut.write(issueLocationWithContext.afterText + QLatin1Char('\n'), Normal);
+ } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) {
+ auto qmlScope = currentScope->getCurrentQMLScope();
+ MethodUsage methodUsage = qmlScope->m_injectedSignalIdentifiers[idLocationPair.first];
+ colorOut.write("Note:", Info);
+ colorOut.write(idLocationPair.first + QString::asprintf(" is accessible in this scope because you are handling a signal at %d:%d\n", methodUsage.loc.startLine, 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("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(")", Hint);
+ colorOut.write(" {...", Normal);
+ }
+ colorOut.write("\n", Normal);
+ }
+ for (auto const& childScope: currentScope->m_childScopes) {
+ workQueue.enqueue(childScope);
+ }
+ }
+ return noUnqualifiedIdentifier;
+}
+
+QMap<QString, LanguageUtils::FakeMetaMethod>const &ScopeTree::methods() const
+{
+ return m_methods;
+}
+
+bool ScopeTree::isIdInCurrentQMlScopes(QString id) const
+{
+ auto qmlScope = getCurrentQMLScope();
+ return qmlScope->m_currentScopeQMLIdentifiers.contains(id);
+}
+
+bool ScopeTree::isIdInCurrentJSScopes(QString id) const
+{
+ auto jsScope = this;
+ while (jsScope) {
+ if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_currentScopeJSIdentifiers.contains(id))
+ return true;
+ jsScope = jsScope->m_parentScope;
+ }
+ return false;
+}
+
+bool ScopeTree::isIdInjectedFromSignal(QString id) const
+{
+ auto qmlScope = getCurrentQMLScope();
+ return qmlScope->m_injectedSignalIdentifiers.contains(id);
+}
+
+const ScopeTree *ScopeTree::getCurrentQMLScope() const
+{
+ auto qmlScope = this;
+ while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) {
+ qmlScope = qmlScope->m_parentScope;
+ }
+ return qmlScope;
+}
+
+ScopeTree *ScopeTree::getCurrentQMLScope()
+{
+ auto qmlScope = this;
+ while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) {
+ qmlScope = qmlScope->m_parentScope;
+ }
+ return qmlScope;
+}
+
+ScopeType ScopeTree::scopeType() {return m_scopeType;}
+
+void ScopeTree::addMethod(LanguageUtils::FakeMetaMethod method)
+{
+ m_methods.insert(method.methodName(), method);
+}
+
+void ScopeTree::addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject)
+{
+ if (metaObject) {
+ auto methodCount = metaObject->methodCount();
+ for (auto i = 0; i < methodCount; ++i) {
+ auto method = metaObject->method(i);
+ this->addMethod(method);
+ }
+ }
+}
diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h
new file mode 100644
index 0000000000..cb495584c1
--- /dev/null
+++ b/tools/qmllint/scopetree.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SCOPETREE_H
+#define SCOPETREE_H
+
+#include "fakemetaobject.h"
+#include "private/qqmljsast_p.h"
+#include "private/qqmljssourcelocation_p.h"
+
+#include <QSet>
+#include <QString>
+#include <QMap>
+
+enum MessageColors{
+ Error,
+ Warning,
+ Info,
+ Normal,
+ Hint
+};
+
+enum class ScopeType
+{
+ JSFunctionScope,
+ JSLexicalScope,
+ QMLScope
+};
+
+struct MethodUsage
+{
+ LanguageUtils::FakeMetaMethod method;
+ QQmlJS::AST::SourceLocation loc;
+};
+
+class ColorOutput;
+
+class ScopeTree {
+public:
+ ScopeTree(ScopeType type, QString name="<none given>", ScopeTree* parentScope=nullptr);
+ ~ScopeTree() {qDeleteAll(m_childScopes);}
+
+ ScopeTree* createNewChildScope(ScopeType type, QString name);
+ ScopeTree* parentScope();
+
+ void insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope);
+ void insertQMLIdentifier(QString id);
+ void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc);
+ void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding
+
+ bool isIdInCurrentScope(QString const &id) const;
+ void addIdToAccssedIfNotInParentScopes(QPair<QString, QQmlJS::AST::SourceLocation> const& id_loc_pair);
+
+ bool isVisualRootScope() const;
+ QString name() const;
+
+ bool recheckIdentifiers(const QString &code, const QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr>& qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const;
+ ScopeType scopeType();
+ void addMethod(LanguageUtils::FakeMetaMethod);
+ void addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject);
+ QMap<QString, LanguageUtils::FakeMetaMethod>const & methods() const;
+
+private:
+ QSet<QString> m_currentScopeJSIdentifiers;
+ QSet<QString> m_currentScopeQMLIdentifiers;
+ QHash<QString, MethodUsage> m_injectedSignalIdentifiers; // need iteration in insertion order for hints, rarely more than 4 parameters needed
+ QMap<QString, LanguageUtils::FakeMetaMethod> m_methods;
+ QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_accessedIdentifiers;
+ QVector<ScopeTree*> m_childScopes;
+ ScopeTree *m_parentScope;
+ QString m_name;
+ ScopeType m_scopeType;
+
+ bool isIdInCurrentQMlScopes(QString id) const;
+ bool isIdInCurrentJSScopes(QString id) const;
+ bool isIdInjectedFromSignal(QString id) const;
+ const ScopeTree* getCurrentQMLScope() const;
+ ScopeTree* getCurrentQMLScope();
+};
+#endif // SCOPETREE_H