From 6fb335ebcec9c5efc6a2d7786e7bbba23c583d48 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 11 Nov 2019 18:18:04 +0100 Subject: qmllint: Cleanup qmllint needed to be refactored before we can add any new functionality: * Fix all the (C++) linter warnings * Remove pointless namespaces * Merge ScopeTree and FakeMetaObject into one class * Remove the "Fake" from class and variable names * Remove dead code * Add "We mean it" warnings everywhere * Unify #include style This also "accidentally" fixes the automatic matching of signal handlers in Connections elements to signals in their parent scopes. Change-Id: Idf8daae34dfd6c0ee00da28b017f921da3eba25c Reviewed-by: Simon Hausmann --- tools/qmllint/componentversion.cpp | 63 +-- tools/qmllint/componentversion.h | 43 +- tools/qmllint/fakemetaobject.cpp | 594 ----------------------- tools/qmllint/fakemetaobject.h | 235 --------- tools/qmllint/findunqualified.cpp | 343 +++++++------ tools/qmllint/findunqualified.h | 63 ++- tools/qmllint/main.cpp | 66 +-- tools/qmllint/metatypes.h | 146 ++++++ tools/qmllint/qcoloroutput.cpp | 157 +++--- tools/qmllint/qcoloroutput.h | 107 ++++ tools/qmllint/qcoloroutput_p.h | 110 ----- tools/qmllint/qmljstypedescriptionreader.cpp | 697 -------------------------- tools/qmllint/qmljstypedescriptionreader.h | 103 ---- tools/qmllint/qmllint.pro | 13 +- tools/qmllint/scopetree.cpp | 229 ++++----- tools/qmllint/scopetree.h | 170 +++++-- tools/qmllint/typedescriptionreader.cpp | 698 +++++++++++++++++++++++++++ tools/qmllint/typedescriptionreader.h | 105 ++++ 18 files changed, 1668 insertions(+), 2274 deletions(-) delete mode 100644 tools/qmllint/fakemetaobject.cpp delete mode 100644 tools/qmllint/fakemetaobject.h create mode 100644 tools/qmllint/metatypes.h create mode 100644 tools/qmllint/qcoloroutput.h delete mode 100644 tools/qmllint/qcoloroutput_p.h delete mode 100644 tools/qmllint/qmljstypedescriptionreader.cpp delete mode 100644 tools/qmllint/qmljstypedescriptionreader.h create mode 100644 tools/qmllint/typedescriptionreader.cpp create mode 100644 tools/qmllint/typedescriptionreader.h (limited to 'tools') diff --git a/tools/qmllint/componentversion.cpp b/tools/qmllint/componentversion.cpp index 3dc4ac37d0..e5047b8302 100644 --- a/tools/qmllint/componentversion.cpp +++ b/tools/qmllint/componentversion.cpp @@ -27,67 +27,24 @@ ****************************************************************************/ #include "componentversion.h" - -#include -#include - -#include - -using namespace LanguageUtils; - -const int ComponentVersion::NoVersion = -1; -const int ComponentVersion::MaxVersion = std::numeric_limits::max(); - -ComponentVersion::ComponentVersion() - : _major(NoVersion), _minor(NoVersion) -{ -} - -ComponentVersion::ComponentVersion(int major, int minor) - : _major(major), _minor(minor) -{ -} +#include ComponentVersion::ComponentVersion(const QString &versionString) - : _major(NoVersion), _minor(NoVersion) { - int dotIdx = versionString.indexOf(QLatin1Char('.')); + const int dotIdx = versionString.indexOf(QLatin1Char('.')); if (dotIdx == -1) return; bool ok = false; - int maybeMajor = versionString.leftRef(dotIdx).toInt(&ok); + const int maybeMajor = versionString.leftRef(dotIdx).toInt(&ok); if (!ok) return; - int maybeMinor = versionString.midRef(dotIdx + 1).toInt(&ok); + const int maybeMinor = versionString.midRef(dotIdx + 1).toInt(&ok); if (!ok) return; - _major = maybeMajor; - _minor = maybeMinor; -} - -ComponentVersion::~ComponentVersion() -{ + m_major = maybeMajor; + m_minor = maybeMinor; } -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(&_major), sizeof(_major)); - hash.addData(reinterpret_cast(&_minor), sizeof(_minor)); -} - -namespace LanguageUtils { - bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) { return lhs.majorVersion() < rhs.majorVersion() @@ -97,7 +54,8 @@ bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs) { return lhs.majorVersion() < rhs.majorVersion() - || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() <= rhs.minorVersion()); + || (lhs.majorVersion() == rhs.majorVersion() + && lhs.minorVersion() <= rhs.minorVersion()); } bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs) @@ -112,12 +70,11 @@ bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs) bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs) { - return lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() == rhs.minorVersion(); + 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 index 9d079f1d30..9c4604b9a3 100644 --- a/tools/qmllint/componentversion.h +++ b/tools/qmllint/componentversion.h @@ -29,36 +29,35 @@ #ifndef COMPONENTVERSION_H #define COMPONENTVERSION_H -#include +// +// 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. -QT_BEGIN_NAMESPACE -class QCryptographicHash; -QT_END_NAMESPACE - -namespace LanguageUtils { +#include class ComponentVersion { - int _major; - int _minor; - public: - static const int NoVersion; - static const int MaxVersion; + static const int NoVersion = -1; - ComponentVersion(); - ComponentVersion(int major, int minor); + ComponentVersion() = default; + ComponentVersion(int major, int minor) : m_major(major), m_minor(minor) {} explicit ComponentVersion(const QString &versionString); - ~ComponentVersion(); - int majorVersion() const - { return _major; } - int minorVersion() const - { return _minor; } + int majorVersion() const { return m_major; } + int minorVersion() const { return m_minor; } - bool isValid() const; - QString toString() const; - void addToHash(QCryptographicHash &hash) const; + bool isValid() const { return m_major >= 0 && m_minor >= 0; } + +private: + int m_major = NoVersion; + int m_minor = NoVersion; }; bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs); @@ -68,6 +67,4 @@ 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 deleted file mode 100644 index 8319ae6713..0000000000 --- a/tools/qmllint/fakemetaobject.cpp +++ /dev/null @@ -1,594 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include "fakemetaobject.h" -#include - -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) -{ m_keys.append(key); } - -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(&len), sizeof(len)); - hash.addData(reinterpret_cast(m_name.constData()), len * sizeof(QChar)); - len = m_keys.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - foreach (const QString &key, m_keys) { - len = key.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); - } -} - -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 += 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(&len), sizeof(len)); - hash.addData(reinterpret_cast(m_name.constData()), len * sizeof(QChar)); - hash.addData(reinterpret_cast(&m_methodAccess), sizeof(m_methodAccess)); - hash.addData(reinterpret_cast(&m_methodTy), sizeof(m_methodTy)); - hash.addData(reinterpret_cast(&m_revision), sizeof(m_revision)); - len = m_paramNames.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - foreach (const QString &pName, m_paramNames) { - len = pName.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(pName.constData()), len * sizeof(QChar)); - } - len = m_paramTypes.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - foreach (const QString &pType, m_paramTypes) { - len = pType.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(pType.constData()), len * sizeof(QChar)); - } - len = m_returnType.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(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(&len), sizeof(len)); - hash.addData(reinterpret_cast(m_propertyName.constData()), len * sizeof(QChar)); - hash.addData(reinterpret_cast(&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(&flags), sizeof(flags)); - len = m_type.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(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::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(&len), sizeof(len)); - hash.addData(reinterpret_cast(m_className.constData()), len * sizeof(QChar)); - len = m_attachedTypeName.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(m_attachedTypeName.constData()), len * sizeof(QChar)); - len = m_defaultPropertyName.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(m_defaultPropertyName.constData()), len * sizeof(QChar)); - len = m_enumNameToIndex.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - { - QStringList keys(m_enumNameToIndex.keys()); - keys.sort(); - foreach (const QString &key, keys) { - len = key.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); - int value = m_enumNameToIndex.value(key); - hash.addData(reinterpret_cast(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint... - m_enums.at(value).addToHash(hash); - } - } - len = m_exports.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - foreach (const Export &e, m_exports) - e.addToHash(hash); // normalize order? - len = m_exports.size(); - hash.addData(reinterpret_cast(&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(&len), sizeof(len)); - hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); - int value = m_propNameToIdx.value(key); - hash.addData(reinterpret_cast(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint... - m_props.at(value).addToHash(hash); - } - } - len = m_superName.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(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(&len), sizeof(len)); - hash.addData(reinterpret_cast(package.constData()), len * sizeof(QChar)); - len = type.size(); - hash.addData(reinterpret_cast(&len), sizeof(len)); - hash.addData(reinterpret_cast(type.constData()), len * sizeof(QChar)); - version.addToHash(hash); - hash.addData(reinterpret_cast(&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 deleted file mode 100644 index ae76596343..0000000000 --- a/tools/qmllint/fakemetaobject.h +++ /dev/null @@ -1,235 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef FAKEMETAOBJECT_H -#define FAKEMETAOBJECT_H - -#include -#include -#include -#include -#include - -#include "componentversion.h" - -QT_BEGIN_NAMESPACE -class QCryptographicHash; -QT_END_NAMESPACE - -namespace LanguageUtils { - -class FakeMetaEnum { - QString m_name; - QStringList m_keys; - -public: - FakeMetaEnum(); - explicit FakeMetaEnum(const QString &name); - - bool isValid() const; - - QString name() const; - void setName(const QString &name); - - void addKey(const QString &key); - 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 Ptr; - typedef QSharedPointer 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 m_exports; - QString m_superName; - QList m_enums; - QHash m_enumNameToIndex; - QList m_props; - QHash m_propNameToIdx; - QList 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 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 index d72f02b94a..ab7ff80609 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -28,18 +28,17 @@ #include "findunqualified.h" #include "scopetree.h" +#include "typedescriptionreader.h" -#include "qmljstypedescriptionreader.h" +#include +#include +#include +#include +#include -#include -#include -#include - -#include -#include -#include -#include -#include +#include +#include +#include static QQmlDirParser createQmldirParserForFile(const QString &filename) { @@ -50,17 +49,17 @@ static QQmlDirParser createQmldirParserForFile(const QString &filename) return parser; } -static QQmlJS::TypeDescriptionReader createQmltypesReaderForFile(QString const &filename) +static TypeDescriptionReader createQmltypesReaderForFile(const QString &filename) { QFile f(filename); f.open(QFile::ReadOnly); - QQmlJS::TypeDescriptionReader reader { filename, f.readAll() }; + TypeDescriptionReader reader { filename, f.readAll() }; return reader; } void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name) { - m_currentScope = m_currentScope->createNewChildScope(type, name); + m_currentScope = m_currentScope->createNewChildScope(type, std::move(name)); } void FindUnqualifiedIDVisitor::leaveEnvironment() @@ -70,7 +69,8 @@ void FindUnqualifiedIDVisitor::leaveEnvironment() enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; -QStringList completeImportPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) +QStringList completeImportPaths(const QString &uri, const QStringList &basePaths, + int vmaj, int vmin) { static const QLatin1Char Slash('/'); static const QLatin1Char Backslash('\\'); @@ -85,15 +85,16 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths { if (version == FullyVersioned) { // extension with fully encoded version number (eg. MyModule.3.2) - return QString::asprintf(".%d.%d", vmaj, vmin); - } else if (version == PartiallyVersioned) { + return QString::fromLatin1(".%1.%2").arg(vmaj).arg(vmin); + } + 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::fromLatin1(".%1").arg(vmaj); + } + // else extension without version number (eg. MyModule) return QString(); }; - auto joinStringRefs = [](const QVector &refs, const QChar &sep) - { + auto joinStringRefs = [](const QVector &refs, const QChar &sep) { QString str; for (auto it = refs.cbegin(); it != refs.cend(); ++it) { if (it != refs.cbegin()) @@ -131,19 +132,19 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths return qmlDirPathsPaths; } -void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int major, int minor) +void FindUnqualifiedIDVisitor::importHelper(QString id, const QString &prefix, int major, int minor) { QPair importId { id, prefix }; - if (m_alreadySeenImports.contains(importId)) { + if (m_alreadySeenImports.contains(importId)) return; - } else { - m_alreadySeenImports.insert(importId); - } + + m_alreadySeenImports.insert(importId); + id = id.replace(QLatin1String("/"), QLatin1String(".")); auto qmltypesPaths = completeImportPaths(id, m_qmltypeDirs, major, minor); - QHash objects; - QList moduleApis; + QHash objects; + QList moduleApis; QStringList dependencies; static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); static const QLatin1String SlashQmldir("/qmldir"); @@ -154,7 +155,7 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int majo for (const QString &import : imports) importHelper(import, prefix, major, minor); - QHash qmlComponents; + QHash qmlComponents; const auto components = reader.components(); for (auto it = components.begin(), end = components.end(); it != end; ++it) { const QString filePath = qmltypesPath + QLatin1Char('/') + it->fileName; @@ -168,15 +169,15 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int majo auto mo = qmlComponents.find(it.key()); if (mo == qmlComponents.end()) - mo = qmlComponents.insert(it.key(), localQmlFile2FakeMetaObject(filePath)); + mo = qmlComponents.insert(it.key(), localQmlFile2ScopeTree(filePath)); (*mo)->addExport( it.key(), reader.typeNamespace(), - LanguageUtils::ComponentVersion(it->majorVersion, it->minorVersion)); + ComponentVersion(it->majorVersion, it->minorVersion)); } for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) { objects.insert(it.key(), - QSharedPointer(it.value())); + QSharedPointer(it.value())); } } if (QFile::exists(qmltypesPath + SlashPluginsDotQmltypes)) { @@ -188,7 +189,7 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int majo } for (auto const &dependency : qAsConst(dependencies)) { auto const split = dependency.split(" "); - auto const id = split.at(0); + 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); @@ -196,27 +197,28 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int majo // 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()); - } + m_exportedName2Scope[prefix + val->className()] = val; + + const auto exports = val->exports(); + for (const auto &valExport : exports) + m_exportedName2Scope[prefix + valExport.type()] = val; + + const auto enums = val->enums(); + for (const auto &valEnum : enums) + m_currentScope->addEnum(valEnum); } } -LanguageUtils::FakeMetaObject * -FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) +ScopeTree *FindUnqualifiedIDVisitor::localQmlFile2ScopeTree(const QString &filePath) { using namespace QQmlJS::AST; - auto fake = new LanguageUtils::FakeMetaObject; + auto scope = new ScopeTree(ScopeType::QMLScope); QString baseName = QFileInfo { filePath }.baseName(); - fake->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); + scope->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); QFile file(filePath); - if (!file.open(QFile::ReadOnly)) { - return fake; - } + if (!file.open(QFile::ReadOnly)) + return scope; + QString code = file.readAll(); file.close(); @@ -226,7 +228,7 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) lexer.setCode(code, 1, true); QQmlJS::Parser parser(&engine); if (!parser.parse()) { - return fake; + return scope; } QQmlJS::AST::UiProgram *program = parser.ast(); auto header = program->headers; @@ -245,7 +247,8 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) if (import->asToken.isValid()) { prefix += import->importId + QLatin1Char('.'); } - importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion); + importHelper(path, prefix, import->version->majorVersion, + import->version->minorVersion); } } header = header->next; @@ -254,12 +257,12 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) // member should be the sole element Q_ASSERT(!member->next); Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition); - auto definition = static_cast(member->member); + auto definition = cast(member->member); auto qualifiedId = definition->qualifiedTypeNameId; while (qualifiedId && qualifiedId->next) { qualifiedId = qualifiedId->next; } - fake->setSuperclassName(qualifiedId->name.toString()); + scope->setSuperclassName(qualifiedId->name.toString()); UiObjectMemberList *initMembers = definition->initializer->members; while (initMembers) { switch (initMembers->member->kind) { @@ -280,28 +283,30 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) break; } case UiObjectMember::Kind_UiPublicMember: { - auto publicMember = static_cast(initMembers->member); + auto publicMember = cast(initMembers->member); switch (publicMember->type) { case UiPublicMember::Signal: { UiParameterList *param = publicMember->parameters; - LanguageUtils::FakeMetaMethod method; - method.setMethodType(LanguageUtils::FakeMetaMethod::Signal); + MetaMethod method; + method.setMethodType(MetaMethod::Signal); method.setMethodName(publicMember->name.toString()); while (param) { method.addParameter(param->name.toString(), param->type->name.toString()); param = param->next; } - fake->addMethod(method); + scope->addMethod(method); break; } case UiPublicMember::Property: { - LanguageUtils::FakeMetaProperty fakeprop { publicMember->name.toString(), - publicMember->typeModifier.toString(), - false, - false, - false, - 0 }; - fake->addProperty(fakeprop); + const MetaProperty property { + publicMember->name.toString(), + publicMember->typeModifier.toString(), + false, + false, + false, + 0 + }; + scope->addProperty(property); break; } } @@ -312,24 +317,21 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) break; } case UiObjectMember::Kind_UiSourceElement: { - auto sourceElement = static_cast(initMembers->member); + auto sourceElement = cast(initMembers->member); if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { - LanguageUtils::FakeMetaMethod method; + MetaMethod method; method.setMethodName(fexpr->name.toString()); - method.setMethodType(LanguageUtils::FakeMetaMethod::Method); + method.setMethodType(MetaMethod::Method); FormalParameterList *parameters = fexpr->formals; while (parameters) { - method.addParameter(parameters->element->bindingIdentifier.toString(), - ""); + method.addParameter(parameters->element->bindingIdentifier.toString(), ""); parameters = parameters->next; } - fake->addMethod(method); + scope->addMethod(method); } else if (ClassExpression *clexpr = sourceElement->sourceElement->asClassDefinition()) { - LanguageUtils::FakeMetaProperty prop { - clexpr->name.toString(), "", false, false, false, 1 - }; - fake->addProperty(prop); + const MetaProperty prop { clexpr->name.toString(), "", false, false, false, 1 }; + scope->addProperty(prop); } else if (cast(sourceElement->sourceElement)) { // nothing to do } else { @@ -348,7 +350,7 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) } initMembers = initMembers->next; } - return fake; + return scope; } void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const QString &prefix) @@ -360,34 +362,30 @@ void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const Q QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; while (it.hasNext()) { - LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next()); - m_exportedName2MetaObject.insert( - prefix + fake->className(), - QSharedPointer(fake)); + const ScopeTree *scope = localQmlFile2ScopeTree(it.next()); + m_exportedName2Scope.insert(prefix + scope->className(), ScopeTree::ConstPtr(scope)); } } -void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString name) +void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QString name) { for (;;) { - auto metaObject = m_exportedName2MetaObject[m_exportedName2MetaObject.contains(name) + auto scope = m_exportedName2Scope[m_exportedName2Scope.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")) { + if (scope) { + const auto properties = scope->properties(); + for (const auto &property : properties) + m_currentScope->insertPropertyIdentifier(property); + + m_currentScope->addMethods(scope->methods()); + name = scope->superclassName(); + if (name.isEmpty() || name == QLatin1String("QObject")) break; - } } else { m_colorOut.write(QLatin1String("warning: "), Warning); - m_colorOut.write(name + QLatin1String(" was not found. Did you add all import paths?\n")); + m_colorOut.write(name + QLatin1String(" was not found." + " Did you add all import paths?\n")); m_unknownImports.insert(name); break; } @@ -404,8 +402,8 @@ void FindUnqualifiedIDVisitor::throwRecursionDepthError() bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) { enterEnvironment(ScopeType::QMLScope, "program"); - QHash objects; - QList moduleApis; + QHash objects; + QList moduleApis; QStringList dependencies; for (auto const &dir : m_qmltypeDirs) { QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter, @@ -420,25 +418,27 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) // 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()); - } + + const auto exports = val->exports(); + for (const auto &valExport : exports) + m_exportedName2Scope[valExport.type()] = val; + + const auto enums = val->enums(); + for (const auto &valEnum : enums) + m_currentScope->addEnum(valEnum); } // 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()] = {}; + // using an empty ScopeTree + m_exportedName2Scope[QFileInfo { m_filePath }.baseName()] = {}; // add QML builtins - m_exportedName2MetaObject["QtObject"] = {}; // QtObject contains nothing of interest + m_exportedName2Scope["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 }; + ScopeTree *scope = new ScopeTree(ScopeType::QMLScope); + scope->addProperty(MetaProperty {"enabled", "bool", false, false, false, 0}); + scope->addProperty(MetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0}); + scope->addProperty(MetaProperty {"target", "QObject", false, false, false, 0}); + m_exportedName2Scope["Connections"] = ScopeTree::ConstPtr { scope }; importDirectory(".", QString()); return true; @@ -518,7 +518,8 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::CaseBlock *) bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *catchStatement) { enterEnvironment(ScopeType::JSLexicalScope, "catch"); - m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(), QQmlJS::AST::VariableScope::Let); + m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(), + QQmlJS::AST::VariableScope::Let); return true; } @@ -529,8 +530,13 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *) 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 analysing unqalified identifiers\n", withStatement->firstSourceLocation().startLine, withStatement->firstSourceLocation().startColumn), Normal); + m_colorOut.write(QString::fromLatin1("Warning: "), Warning); + m_colorOut.write(QString::fromLatin1( + "%1:%2: with statements are strongly discouraged in QML " + "and might cause false positives when analysing unqalified identifiers\n") + .arg(withStatement->firstSourceLocation().startLine) + .arg(withStatement->firstSourceLocation().startColumn), + Normal); enterEnvironment(ScopeType::JSLexicalScope, "with"); return true; } @@ -563,13 +569,12 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) auto name = uisb->qualifiedId->name; if (name == QLatin1String("id")) { // found id - auto expstat = static_cast(uisb->statement); - auto identexp = static_cast(expstat->expression); + auto expstat = cast(uisb->statement); + auto identexp = cast(expstat->expression); QString elementName = m_currentScope->name(); - m_qmlid2meta.insert(identexp->name.toString(), m_exportedName2MetaObject[elementName]); - if (m_currentScope->isVisualRootScope()) { + m_qmlid2scope.insert(identexp->name.toString(), m_exportedName2Scope[elementName]); + if (m_currentScope->isVisualRootScope()) m_rootId = identexp->name.toString(); - } } else { const QString signal = signalName(name); if (signal.isEmpty()) @@ -582,7 +587,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) const auto statement = uisb->statement; if (statement->kind == Node::Kind::Kind_ExpressionStatement) { - if (static_cast(statement)->expression->asFunctionDefinition()) { + if (cast(statement)->expression->asFunctionDefinition()) { // functions are already handled // they do not get names inserted according to the signal, but access their formal // parameters @@ -607,37 +612,42 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm) { // property bool inactive: !active // extract name inactive - m_currentScope->insertPropertyIdentifier(uipm->name.toString()); + m_currentScope->insertPropertyIdentifier(MetaProperty( + uipm->name.toString(), + // TODO: signals, complex types etc. + uipm->memberType ? uipm->memberType->name.toString() : QString(), + uipm->typeModifier == QLatin1String("list"), + !uipm->isReadonlyMember, + false, 0)); return true; } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) { auto name = idexp->name; - if (!m_exportedName2MetaObject.contains(name.toString())) { + if (!m_exportedName2Scope.contains(name.toString())) { m_currentScope->addIdToAccssedIfNotInParentScopes( { name.toString(), idexp->firstSourceLocation() }, m_unknownImports); } return true; } -FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, - const QString &code, const QString &fileName, - bool silent) +FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList qmltypeDirs, QString code, + QString fileName, bool silent) : m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global" }), m_currentScope(m_rootScope.get()), - m_qmltypeDirs(qmltypeDirs), - m_code(code), + m_qmltypeDirs(std::move(qmltypeDirs)), + m_code(std::move(code)), m_rootId(QLatin1String("")), - m_filePath(fileName), + m_filePath(std::move(fileName)), m_colorOut(silent) { // 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); + m_colorOut.insertMapping(Error, ColorOutput::RedForeground); + m_colorOut.insertMapping(Warning, ColorOutput::PurpleForeground); + m_colorOut.insertMapping(Info, ColorOutput::BlueForeground); + m_colorOut.insertMapping(Normal, ColorOutput::DefaultColor); + m_colorOut.insertMapping(Hint, ColorOutput::GreenForeground); QLatin1String jsGlobVars[] = { /* Not listed on the MDN page; browser and QML extensions: */ // console/debug api @@ -645,32 +655,36 @@ FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDir // garbage collector QLatin1String("gc"), // i18n - QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"), + QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), + QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"), // XMLHttpRequest QLatin1String("XMLHttpRequest") }; - for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr; ++globalName) { - m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), QQmlJS::AST::VariableScope::Const); + for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; + *globalName != nullptr; + ++globalName) { + m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), + QQmlJS::AST::VariableScope::Const); } for (const auto& jsGlobVar: jsGlobVars) m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const); } -FindUnqualifiedIDVisitor::~FindUnqualifiedIDVisitor() = default; - bool FindUnqualifiedIDVisitor::check() { if (m_visitFailed) return false; // 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); + for (auto const &outstandingConnection: m_outstandingConnections) { + auto targetScope = m_qmlid2scope[outstandingConnection.targetName]; + if (outstandingConnection.scope) + outstandingConnection.scope->addMethods(targetScope->methods()); QScopedValueRollback 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); + return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_rootScope.get(), m_rootId, + m_colorOut); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) @@ -686,18 +700,16 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) 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 { + auto name = fexpr->name.toString(); + if (!name.isEmpty()) { + if (m_currentScope->scopeType() == ScopeType::QMLScope) + m_currentScope->addMethod(MetaMethod(name, QLatin1String("void"))); + else m_currentScope->insertJSIdentifier(name, VariableScope::Const); - } + enterEnvironment(ScopeType::JSFunctionScope, name); + } else { + enterEnvironment(ScopeType::JSFunctionScope, QLatin1String("")); } - QString name = fexpr->name.toString(); - if (name.isEmpty()) - name = ""; - enterEnvironment(ScopeType::JSFunctionScope, name); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr) @@ -743,7 +755,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) 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 + // TODO: do not put imported ids into the same space as qml IDs + m_qmlid2scope.insert(import->importId.toString(), {}); } if (import->version) { auto uri = import->importUri; @@ -761,14 +774,17 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) { - m_currentScope->insertQMLIdentifier(uied->name.toString()); + MetaEnum qmlEnum(uied->name.toString()); + for (const auto *member = uied->members; member; member = member->next) + qmlEnum.addKey(member->member.toString()); + m_currentScope->addEnum(qmlEnum); 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; @@ -777,8 +793,13 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) id = id->next; } name.chop(1); + + const MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, 0); + m_currentScope->addProperty(prop); + 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 + // there is no typeinfo for Component and QtObject, but they also have no interesting properties + if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) return true; importExportedNames(prefix, name); return true; @@ -791,6 +812,8 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *) bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) { + using namespace QQmlJS::AST; + QString name {}; auto id = uiod->qualifiedTypeNameId; QStringRef prefix = uiod->qualifiedTypeNameId->name; @@ -802,8 +825,11 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) 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 + + // there is no typeinfo for Component + if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) return true; + importExportedNames(prefix, name); if (name.endsWith("Connections")) { QString target; @@ -825,25 +851,26 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) } member = member->next; } - LanguageUtils::FakeMetaObject::ConstPtr metaObject {}; + ScopeTree::ConstPtr targetScope; 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()]; + targetScope = m_exportedName2Scope[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; + auto scopeIt = m_qmlid2scope.find(target); + if (scopeIt != m_qmlid2scope.end()) { + targetScope = *scopeIt; } else { m_outstandingConnections.push_back({target, m_currentScope, uiod}); return false; // visit children later once target is known } } - m_currentScope->addMethodsFromMetaObject(metaObject); + if (targetScope) + m_currentScope->addMethods(targetScope->methods()); } return true; } diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 80413bd402..8b79918d90 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -29,32 +29,41 @@ #ifndef FINDUNQUALIFIED_H #define FINDUNQUALIFIED_H -#include "qmljstypedescriptionreader.h" -#include "qcoloroutput_p.h" - -#include -#include - -#include - -class ScopeTree; -enum class ScopeType; - -class FindUnqualifiedIDVisitor : public QQmlJS::AST::Visitor { - +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "typedescriptionreader.h" +#include "scopetree.h" +#include "qcoloroutput.h" + +#include +#include + +#include + +class FindUnqualifiedIDVisitor : public QQmlJS::AST::Visitor +{ + Q_DISABLE_COPY_MOVE(FindUnqualifiedIDVisitor) public: - explicit FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, const QString& code, - const QString& fileName, bool silent); - ~FindUnqualifiedIDVisitor() override; + explicit FindUnqualifiedIDVisitor(QStringList qmltypeDirs, QString code, + QString fileName, bool silent); + ~FindUnqualifiedIDVisitor() override = default; bool check(); private: QScopedPointer m_rootScope; - ScopeTree *m_currentScope; - QHash m_exportedName2MetaObject; + ScopeTree *m_currentScope = nullptr; + QHash m_exportedName2Scope; QStringList m_qmltypeDirs; - const QString& m_code; - QHash m_qmlid2meta; + QString m_code; + QHash m_qmlid2scope; QString m_rootId; QString m_filePath; QSet> m_alreadySeenImports; @@ -62,17 +71,22 @@ private: ColorOutput m_colorOut; bool m_visitFailed = false; - struct OutstandingConnection {QString targetName; ScopeTree *scope; QQmlJS::AST::UiObjectDefinition *uiod;}; + struct OutstandingConnection + { + QString targetName; + ScopeTree *scope; + QQmlJS::AST::UiObjectDefinition *uiod; + }; QVarLengthArray 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 importHelper(QString id, const QString &prefix, int major, int minor); + ScopeTree* localQmlFile2ScopeTree(const QString &filePath); void importDirectory(const QString &directory, const QString &prefix); - void importExportedNames(QStringRef prefix, QString name); + void importExportedNames(const QStringRef &prefix, QString name); void throwRecursionDepthError() override; @@ -130,5 +144,4 @@ private: bool visit(QQmlJS::AST::PatternElement *) override; }; - #endif // FINDUNQUALIFIED_H diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 232b31934e..0a0e669e94 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -26,27 +26,29 @@ ** ****************************************************************************/ -#include -#include -#include +#include "findunqualified.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + #if QT_CONFIG(commandlineparser) -#include +#include #endif -#include #ifndef QT_BOOTSTRAPPED -#include +#include #endif -#include -#include -#include -#include -#include - -#include "findunqualified.h" - -static bool lint_file(const QString &filename, const bool silent, const bool warnUnqualied, QStringList const &qmltypeDirs) +static bool lint_file(const QString &filename, const bool silent, const bool warnUnqualied, + const QStringList &qmltypeDirs) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -63,17 +65,20 @@ static bool lint_file(const QString &filename, const bool silent, const bool war QFileInfo info(filename); const QString lowerSuffix = info.suffix().toLower(); - const bool isJavaScript = (lowerSuffix == QLatin1String("js") || lowerSuffix == QLatin1String("mjs")); const bool isESModule = lowerSuffix == QLatin1String("mjs"); - lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript); + const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js"); + + lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/ !isJavaScript); QQmlJS::Parser parser(&engine); - bool success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) : parser.parse(); + bool success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) + : parser.parse(); if (!success && !silent) { const auto diagnosticMessages = parser.diagnosticMessages(); for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - qWarning("%s:%d : %s", qPrintable(filename), m.line, qPrintable(m.message)); + qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") + .arg(filename).arg(m.line).arg(m.message); } } @@ -98,10 +103,12 @@ int main(int argv, char *argc[]) parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption silentOption(QStringList() << "s" << "silent", QLatin1String("Don't output syntax errors")); + 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")); + QCommandLineOption checkUnqualified(QStringList() << "U" << "check-unqualified", + QLatin1String("Warn about unqualified identifiers")); parser.addOption(checkUnqualified); QCommandLineOption qmltypesDirsOption( @@ -111,7 +118,8 @@ int main(int argv, char *argc[]) QLatin1String("directory")); parser.addOption(qmltypesDirsOption); - parser.addPositionalArgument(QLatin1String("files"), QLatin1String("list of qml or js files to verify")); + parser.addPositionalArgument(QLatin1String("files"), + QLatin1String("list of qml or js files to verify")); parser.process(app); @@ -123,12 +131,14 @@ 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), QLatin1String(".")}; -#else - : QStringList{QLatin1String(".")}; -#endif + QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption) + ? parser.values(qmltypesDirsOption) +# ifndef QT_BOOTSTRAPPED + : QStringList { QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath), + QLatin1String(".") }; +# else + : QStringList { QLatin1String(".") }; +# endif #else bool silent = false; bool warnUnqualified = false; diff --git a/tools/qmllint/metatypes.h b/tools/qmllint/metatypes.h new file mode 100644 index 0000000000..24f8aa291e --- /dev/null +++ b/tools/qmllint/metatypes.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef METATYPES_H +#define METATYPES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include + +class MetaEnum +{ + QString m_name; + QStringList m_keys; + +public: + MetaEnum() = default; + explicit MetaEnum(QString name) : m_name(std::move(name)) {} + + bool isValid() const { return !m_name.isEmpty(); } + + QString name() const { return m_name; } + void setName(const QString &name) { m_name = name; } + + void addKey(const QString &key) { m_keys.append(key); } + QStringList keys() const { return m_keys; } +}; + +class MetaMethod +{ +public: + enum Type { + Signal, + Slot, + Method + }; + + enum Access { + Private, + Protected, + Public + }; + + MetaMethod() = default; + explicit MetaMethod(QString name, QString returnType = QString()) + : m_name(std::move(name)) + , m_returnType(std::move(returnType)) + , m_methodType(Method) + , m_methodAccess(Public) + {} + + QString methodName() const { return m_name; } + void setMethodName(const QString &name) { m_name = name; } + + void setReturnType(const QString &type) { m_returnType = type; } + + QStringList parameterNames() const { return m_paramNames; } + QStringList parameterTypes() const { return m_paramTypes; } + void addParameter(const QString &name, const QString &type) + { + m_paramNames.append(name); + m_paramTypes.append(type); + } + + int methodType() const { return m_methodType; } + void setMethodType(Type methodType) { m_methodType = methodType; } + + Access access() const { return m_methodAccess; } + + int revision() const { return m_revision; } + void setRevision(int r) { m_revision = r; } + +private: + QString m_name; + QString m_returnType; + QStringList m_paramNames; + QStringList m_paramTypes; + Type m_methodType = Signal; + Access m_methodAccess = Private; + int m_revision = 0; +}; + +class MetaProperty +{ + QString m_propertyName; + QString m_type; + bool m_isList; + bool m_isWritable; + bool m_isPointer; + int m_revision; + +public: + MetaProperty(QString name, QString type, + bool isList, bool isWritable, bool isPointer, int revision) + : m_propertyName(std::move(name)) + , m_type(std::move(type)) + , m_isList(isList) + , m_isWritable(isWritable) + , m_isPointer(isPointer) + , m_revision(revision) + {} + + QString name() const { return m_propertyName; } + QString typeName() const { return m_type; } + + bool isList() const { return m_isList; } + bool isWritable() const { return m_isWritable; } + bool isPointer() const { return m_isPointer; } + int revision() const { return m_revision; } +}; + +#endif // METATYPES_H diff --git a/tools/qmllint/qcoloroutput.cpp b/tools/qmllint/qcoloroutput.cpp index eb4c721663..84bd22c428 100644 --- a/tools/qmllint/qcoloroutput.cpp +++ b/tools/qmllint/qcoloroutput.cpp @@ -26,72 +26,76 @@ ** ****************************************************************************/ -#include -#include -#include +#include "qcoloroutput.h" + +#include +#include +#include #ifndef Q_OS_WIN #include #endif -#include "qcoloroutput_p.h" - class ColorOutputPrivate { public: - ColorOutputPrivate(bool silent) : currentColorID(-1), silent(silent) - + ColorOutputPrivate(bool silent) : m_currentColorID(-1), m_silent(silent) { - /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, performance - * is considered of lower priority. + /* - 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(); + m_coloringEnabled = isColoringPossible(); } - ColorOutput::ColorMapping colorMapping; - int currentColorID; - bool coloringEnabled; - bool silent; - static const char *const foregrounds[]; static const char *const backgrounds[]; - inline void write(const QString &msg) - { - m_out.write(msg.toLocal8Bit()); - } + inline void write(const QString &msg) { m_out.write(msg.toLocal8Bit()); } static QString escapeCode(const QString &in) { + const ushort escapeChar = 0x1B; QString result; - result.append(QChar(0x1B)); + result.append(QChar(escapeChar)); result.append(QLatin1Char('[')); result.append(in); result.append(QLatin1Char('m')); return result; } + void insertColor(int id, ColorOutput::ColorCode code) { m_colorMapping.insert(id, code); } + ColorOutput::ColorCode color(int id) const { return m_colorMapping.value(id); } + bool containsColor(int id) const { return m_colorMapping.contains(id); } + + bool isSilent() const { return m_silent; } + void setCurrentColorID(int colorId) { m_currentColorID = colorId; } + + bool coloringEnabled() const { return m_coloringEnabled; } + private: QFile m_out; + ColorOutput::ColorMapping m_colorMapping; + int m_currentColorID; + bool m_coloringEnabled; + bool m_silent; /*! 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 +#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 } }; @@ -128,7 +132,6 @@ const char *const ColorOutputPrivate::backgrounds[] = /*! \class ColorOutput - \since 4.4 \nonreentrant \brief Outputs colored messages to \c stderr. \internal @@ -214,41 +217,18 @@ const char *const ColorOutputPrivate::backgrounds[] = 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(bool silent) : d(new ColorOutputPrivate(silent)) -{ -} +ColorOutput::ColorOutput(bool silent) : d(new ColorOutputPrivate(silent)) {} -ColorOutput::~ColorOutput() = default; // must be here so that QScopedPointer has access to the complete type +// must be here so that QScopedPointer has access to the complete type +ColorOutput::~ColorOutput() = default; /*! - Sends \a message to \c stderr, using the color looked up in colorMapping() using \a colorID. + Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID. - If \a color isn't available in colorMapping(), result and behavior is undefined. + If \a color isn't available in the color mapping, 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. @@ -259,7 +239,7 @@ ColorOutput::~ColorOutput() = default; // must be here so that QScopedPointer ha */ void ColorOutput::write(const QString &message, int colorID) { - if (!d->silent) + if (!d->isSilent()) d->write(colorify(message, colorID)); } @@ -271,7 +251,7 @@ void ColorOutput::write(const QString &message, int colorID) */ void ColorOutput::writeUncolored(const QString &message) { - if (!d->silent) + if (!d->isSilent()) d->write(message + QLatin1Char('\n')); } @@ -285,61 +265,56 @@ void ColorOutput::writeUncolored(const QString &message) */ 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."); + Q_ASSERT_X(colorID == -1 || d->containsColor(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; + d->setCurrentColorID(colorID); - if (d->coloringEnabled && colorID != -1) - { - const int color(d->colorMapping.value(colorID)); + if (d->coloringEnabled() && colorID != -1) { + const int color = d->color(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; + const int foregroundCode = (color & ForegroundMask) >> ForegroundShift; + const int backgroundCode = (color & BackgroundMask) >> BackgroundShift; QString finalMessage; bool closureNeeded = false; - if (foregroundCode) - { - finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); + if (foregroundCode > 0) { + finalMessage.append( + ColorOutputPrivate::escapeCode( + QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); closureNeeded = true; } - if (backgroundCode) - { - finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); + if (backgroundCode > 0) { + finalMessage.append( + ColorOutputPrivate::escapeCode( + QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); closureNeeded = true; } finalMessage.append(message); if (closureNeeded) - { - finalMessage.append(QChar(0x1B)); - finalMessage.append(QLatin1String("[0m")); - } + finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String("0"))); return finalMessage; } - else - return message; + + 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) +void ColorOutput::insertMapping(int colorID, const ColorCode colorCode) { - d->colorMapping.insert(colorID, colorCode); + d->insertColor(colorID, colorCode); } diff --git a/tools/qmllint/qcoloroutput.h b/tools/qmllint/qcoloroutput.h new file mode 100644 index 0000000000..92f4b47ff0 --- /dev/null +++ b/tools/qmllint/qcoloroutput.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 QCOLOROUTPUT_H +#define QCOLOROUTPUT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include + +class 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 + }; + + using ColorCode = QFlags; + using ColorMapping = QHash; + + ColorOutput(bool silent); + ~ColorOutput(); + + void insertMapping(int colorID, 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 d; + Q_DISABLE_COPY_MOVE(ColorOutput) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ColorOutput::ColorCode) + +#endif // QCOLOROUTPUT_H diff --git a/tools/qmllint/qcoloroutput_p.h b/tools/qmllint/qcoloroutput_p.h deleted file mode 100644 index aefa765a87..0000000000 --- a/tools/qmllint/qcoloroutput_p.h +++ /dev/null @@ -1,110 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -// -// 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 -#include -#include - -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 ColorCode; - typedef QHash ColorMapping; - - ColorOutput(bool silent); - ~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 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 deleted file mode 100644 index b8aecdddb1..0000000000 --- a/tools/qmllint/qmljstypedescriptionreader.cpp +++ /dev/null @@ -1,697 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qmljstypedescriptionreader.h" - -#include -#include -#include - -#include - -#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 *objects, - QList *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->headers->headerItem)) { - addError(SourceLocation(), tr("Expected a single import.")); - return; - } - - UiImport *import = AST::cast(ast->headers->headerItem); - if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) { - addError(import->importToken, tr("Expected import of QtQuick.tooling.")); - return; - } - - if (!import->version) { - addError(import->firstSourceLocation(), tr("Import statement without version.")); - return; - } - - if (import->version->majorVersion != 1) { - addError(import->version->firstSourceLocation(), tr("Major version different from 1 not supported.")); - return; - } - - if (!ast->members || !ast->members->member || ast->members->next) { - addError(SourceLocation(), tr("Expected document to contain a single object definition.")); - return; - } - - UiObjectDefinition *module = AST::cast(ast->members->member); - if (!module) { - addError(SourceLocation(), tr("Expected document to contain a single object definition.")); - return; - } - - if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) { - addError(SourceLocation(), tr("Expected document to contain a Module {} member.")); - return; - } - - readModule(module); -} - -void TypeDescriptionReader::readModule(UiObjectDefinition *ast) -{ - for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { - UiObjectMember *member = it->member; - UiObjectDefinition *component = AST::cast(member); - - UiScriptBinding *script = AST::cast(member); - if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) { - readDependencies(script); - continue; - } - - QString typeName; - if (component) - typeName = toString(component->qualifiedTypeNameId); - - if (!component || (typeName != QLatin1String("Component") && 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(ast->statement); - if (!stmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions")); - return; - } - ArrayPattern *exp = AST::cast(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(l->element->initializer); - StringLiteral *str = AST::cast(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(member); - UiScriptBinding *script = AST::cast(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(""), 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(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(member); - UiScriptBinding *script = AST::cast(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(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(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(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(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected string after colon.")); - return QString(); - } - - StringLiteral *stringLit = AST::cast(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(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon.")); - return false; - } - - TrueLiteral *trueLit = AST::cast(expStmt->expression); - FalseLiteral *falseLit = AST::cast(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(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon.")); - return 0; - } - - NumericLiteral *numericLit = AST::cast(expStmt->expression); - if (!numericLit) { - addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); - return 0; - } - - return numericLit->value; -} - -ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast) -{ - ComponentVersion invalidVersion; - - if (!ast || !ast->statement) { - addError((ast ? ast->colonToken : SourceLocation()), tr("Expected numeric literal after colon.")); - return invalidVersion; - } - - ExpressionStatement *expStmt = AST::cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon.")); - return invalidVersion; - } - - NumericLiteral *numericLit = AST::cast(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(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(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected array of strings after colon.")); - return; - } - - ArrayPattern *arrayLit = AST::cast(expStmt->expression); - if (!arrayLit) { - addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon.")); - return; - } - - for (PatternElementList *it = arrayLit->elements; it; it = it->next) { - StringLiteral *stringLit = AST::cast(it->element->initializer); - if (!stringLit) { - addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only string literal members.")); - return; - } - QString exp = stringLit->value.toString(); - int slashIdx = exp.indexOf(QLatin1Char('/')); - int spaceIdx = exp.indexOf(QLatin1Char(' ')); - ComponentVersion version(exp.mid(spaceIdx + 1)); - - if (spaceIdx == -1 || !version.isValid()) { - addError(stringLit->firstSourceLocation(), tr("Expected string literal to contain 'Package/Name major.minor' or 'Name major.minor'.")); - continue; - } - QString package; - if (slashIdx != -1) - package = exp.left(slashIdx); - QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1)); - - // ### relocatable exports where package is empty? - 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(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected array of numbers after colon.")); - return; - } - - ArrayPattern *arrayLit = AST::cast(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(it->element->initializer); - if (!numberLit) { - addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only number literal members.")); - return; - } - - if (exportIndex >= exportCount) { - addError(numberLit->firstSourceLocation(), tr("Meta object revision without matching export.")); - return; - } - - const double v = numberLit->value; - const int metaObjectRevision = static_cast(v); - if (metaObjectRevision != v) { - addError(numberLit->firstSourceLocation(), tr("Expected integer.")); - return; - } - - 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; - } - - auto *expStmt = AST::cast(ast->statement); - if (!expStmt) { - addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon.")); - return; - } - - if (auto *objectLit = AST::cast(expStmt->expression)) { - for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { - if (PatternProperty *assignement = it->property) { - if (auto *name = AST::cast(assignement->name)) { - fme->addKey(name->id.toString()); - continue; - } - } - addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); - } - } else if (auto *arrayLit = AST::cast(expStmt->expression)) { - for (PatternElementList *it = arrayLit->elements; it; it = it->next) { - if (PatternElement *element = it->element) { - if (auto *name = AST::cast(element->initializer)) { - fme->addKey(name->value.toString()); - continue; - } - } - addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); - } - } else { - addError(ast->statement->firstSourceLocation(), - tr("Expected either array or object literal as enum definition.")); - } -} diff --git a/tools/qmllint/qmljstypedescriptionreader.h b/tools/qmllint/qmljstypedescriptionreader.h deleted file mode 100644 index df215af8d2..0000000000 --- a/tools/qmllint/qmljstypedescriptionreader.h +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QMLJSTYPEDESCRIPTIONREADER_H -#define QMLJSTYPEDESCRIPTIONREADER_H - -#include -#include "fakemetaobject.h" - -// for Q_DECLARE_TR_FUNCTIONS -#include - -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 *objects, - QList *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 *_objects; - QList *_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 76363a7cd8..2631768b81 100644 --- a/tools/qmllint/qmllint.pro +++ b/tools/qmllint/qmllint.pro @@ -4,11 +4,10 @@ QT = core qmldevtools-private SOURCES += main.cpp \ componentversion.cpp \ - fakemetaobject.cpp \ findunqualified.cpp \ - qmljstypedescriptionreader.cpp \ qcoloroutput.cpp \ - scopetree.cpp + scopetree.cpp \ + typedescriptionreader.cpp QMAKE_TARGET_DESCRIPTION = QML Syntax Verifier @@ -16,8 +15,8 @@ load(qt_tool) HEADERS += \ componentversion.h \ - fakemetaobject.h \ findunqualified.h \ - qmljstypedescriptionreader.h \ - qcoloroutput_p.h \ - scopetree.h + metatypes.h \ + qcoloroutput.h \ + scopetree.h \ + typedescriptionreader.h diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index 1e873cca8f..7e9be92673 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -27,28 +27,27 @@ ****************************************************************************/ #include "scopetree.h" +#include "qcoloroutput.h" -#include "qcoloroutput_p.h" +#include #include -#include - ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) - : m_parentScope(parentScope), m_name(name), m_scopeType(type) {} + : m_parentScope(parentScope), m_name(std::move(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}; +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, std::move(name), this}; m_childScopes.push_back(childScope); return childScope; } -ScopeTree *ScopeTree::parentScope() { - return m_parentScope; -} - -void ScopeTree::insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope) +void ScopeTree::insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope) { Q_ASSERT(m_scopeType != ScopeType::QMLScope); if (scope == QQmlJS::AST::VariableScope::Var) { @@ -56,29 +55,25 @@ void ScopeTree::insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope) while (targetScope->scopeType() != ScopeType::JSFunctionScope) { targetScope = targetScope->m_parentScope; } - targetScope->m_currentScopeJSIdentifiers.insert(id); + targetScope->m_jsIdentifiers.insert(id); } else { - m_currentScopeJSIdentifiers.insert(id); + m_jsIdentifiers.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, bool hasMultilineHandlerBody) +void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &method, + const QQmlJS::AST::SourceLocation &loc, + bool hasMultilineHandlerBody) { Q_ASSERT(m_scopeType == ScopeType::QMLScope); m_injectedSignalIdentifiers.insert(id, {method, loc, hasMultilineHandlerBody}); } -void ScopeTree::insertPropertyIdentifier(QString id) +void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) { - this->insertQMLIdentifier(id); - LanguageUtils::FakeMetaMethod method( id + QLatin1String("Changed"), "void"); - this->addMethod(method); + addProperty(property); + MetaMethod method(property.name() + QLatin1String("Changed"), "void"); + addMethod(method); } void ScopeTree::addUnmatchedSignalHandler(const QString &handler, @@ -92,52 +87,62 @@ bool ScopeTree::isIdInCurrentScope(const QString &id) const return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); } -void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair &id_loc_pair, const QSet& unknownImports) { +void ScopeTree::addIdToAccssedIfNotInParentScopes( + const QPair &idLocationPair, + const QSet &unknownImports) +{ // also do not add id if it is parent // parent is almost always defined valid in QML, and if we could not find a definition for the current QML component // not skipping "parent" will lead to many false positives // Moreover, if the top level item is Item or inherits from it, it will have a parent property to which we would point the user // which makes for a very nonsensical warning - auto qmlScope = getCurrentQMLScope(); - if (!isIdInCurrentScope(id_loc_pair.first) && !(id_loc_pair.first == QLatin1String("parent") && qmlScope && unknownImports.contains(qmlScope->name()))) { - m_accessedIdentifiers.push_back(id_loc_pair); + const auto *qmlScope = currentQMLScope(); + if (!isIdInCurrentScope(idLocationPair.first) + && !(idLocationPair.first == QLatin1String("parent") + && qmlScope && unknownImports.contains(qmlScope->name()))) { + m_accessedIdentifiers.push_back(idLocationPair); } } bool ScopeTree::isVisualRootScope() const { - return m_parentScope && m_parentScope->m_parentScope && m_parentScope->m_parentScope->m_parentScope == nullptr; + return m_parentScope && m_parentScope->m_parentScope + && m_parentScope->m_parentScope->m_parentScope == nullptr; } -QString ScopeTree::name() const +class IssueLocationWithContext { - return m_name; -} - -struct IssueLocationWithContext -{ - IssueLocationWithContext(const QString& code, QQmlJS::AST::SourceLocation location) { +public: + IssueLocationWithContext(const QString &code, const 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)); + m_beforeText = code.midRef(before + 1, int(location.offset - (before + 1))); + m_issueText = code.midRef(location.offset, location.length); + int after = code.indexOf('\n', int(location.offset + location.length)); + m_afterText = code.midRef(int(location.offset + location.length), + int(after - (location.offset+location.length))); } - QStringRef beforeText; - QStringRef issueText; - QStringRef afterText; + QStringRef beforeText() const { return m_beforeText; } + QStringRef issueText() const { return m_issueText; } + QStringRef afterText() const { return m_afterText; } + +private: + QStringRef m_beforeText; + QStringRef m_issueText; + QStringRef m_afterText; }; -bool ScopeTree::recheckIdentifiers(const QString& code, const QHash &qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput& colorOut) const +bool ScopeTree::recheckIdentifiers( + const QString &code, const QHash &qmlIDs, + const ScopeTree *root, const QString &rootId, ColorOutput &colorOut) const { bool noUnqualifiedIdentifier = true; // revisit all scopes - QQueue workQueue; + QQueue workQueue; workQueue.enqueue(this); while (!workQueue.empty()) { - const ScopeTree* currentScope = workQueue.dequeue(); + const ScopeTree *currentScope = workQueue.dequeue(); for (const auto &handler : currentScope->m_unmatchedSignalHandlers) { colorOut.write("Warning: ", Warning); colorOut.write(QString::fromLatin1( @@ -147,7 +152,7 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHashm_accessedIdentifiers) { + for (const auto &idLocationPair : qAsConst(currentScope->m_accessedIdentifiers)) { if (qmlIDs.contains(idLocationPair.first)) continue; if (currentScope->isIdInCurrentScope(idLocationPair.first)) { @@ -156,17 +161,17 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash 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; - } + const auto firstElement = root->m_childScopes[0]->m_childScopes[0]; + if (firstElement->m_properties.contains(idLocationPair.first) + || firstElement->m_methods.contains(idLocationPair.first) + || firstElement->m_enums.contains(idLocationPair.first)) { colorOut.write("Note: ", Info); colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal ); colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); @@ -175,92 +180,87 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHashisIdInjectedFromSignal(idLocationPair.first)) { - auto qmlScope = currentScope->getCurrentQMLScope(); - auto methodUsages = qmlScope->m_injectedSignalIdentifiers.values(idLocationPair.first); + auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers + .values(idLocationPair.first); auto location = idLocationPair.second; // sort the list of signal handlers by their occurrence in the source code // then, we select the first one whose location is after the unqualified id // and go one step backwards to get the one which we actually need - std::sort(methodUsages.begin(), methodUsages.end(), [](const MethodUsage m1, const MethodUsage m2) { - return m1.loc.startLine < m2.loc.startLine || (m1.loc.startLine == m2.loc.startLine && m1.loc.startColumn < m2.loc.startColumn); + std::sort(methodUsages.begin(), methodUsages.end(), + [](const MethodUsage &m1, const MethodUsage &m2) { + return m1.loc.startLine < m2.loc.startLine + || (m1.loc.startLine == m2.loc.startLine + && m1.loc.startColumn < m2.loc.startColumn); }); - auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(), [&location](MethodUsage methodUsage) { - return location.startLine < methodUsage.loc.startLine || (location.startLine == methodUsage.loc.startLine && location.startColumn < methodUsage.loc.startColumn); + auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(), + [&location](const MethodUsage &methodUsage) { + return location.startLine < methodUsage.loc.startLine + || (location.startLine == methodUsage.loc.startLine + && location.startColumn < methodUsage.loc.startColumn); }); auto methodUsage = *(--oneBehindIt); colorOut.write("Note:", Info); - colorOut.write(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( + idLocationPair.first + QString::fromLatin1( + " is accessible in this scope because " + "you are handling a signal at %1:%2\n") + .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn), + Normal); colorOut.write("Consider using a function instead\n", Normal); IssueLocationWithContext context {code, methodUsage.loc}; - colorOut.write(context.beforeText + QLatin1Char(' ')); + colorOut.write(context.beforeText() + QLatin1Char(' ')); colorOut.write(methodUsage.hasMultilineHandlerBody ? "function(" : "(", Hint); const auto parameters = methodUsage.method.parameterNames(); for (int numParams = parameters.size(); numParams > 0; --numParams) { colorOut.write(parameters.at(parameters.size() - numParams), Hint); - if (numParams > 1) { + if (numParams > 1) colorOut.write(", ", Hint); - } } colorOut.write(methodUsage.hasMultilineHandlerBody ? ")" : ") => ", Hint); colorOut.write(" {...", Normal); } colorOut.write("\n\n\n", Normal); } - for (auto const& childScope: currentScope->m_childScopes) { + for (auto const& childScope: currentScope->m_childScopes) workQueue.enqueue(childScope); - } } return noUnqualifiedIdentifier; } -QMapconst &ScopeTree::methods() const +bool ScopeTree::isIdInCurrentQMlScopes(const QString &id) const { - return m_methods; + const auto *qmlScope = currentQMLScope(); + return qmlScope->m_properties.contains(id) + || qmlScope->m_methods.contains(id) + || qmlScope->m_enums.contains(id); } -bool ScopeTree::isIdInCurrentQMlScopes(QString id) const -{ - auto qmlScope = getCurrentQMLScope(); - return qmlScope->m_currentScopeQMLIdentifiers.contains(id) || qmlScope->m_methods.contains(id); -} - -bool ScopeTree::isIdInCurrentJSScopes(QString id) const +bool ScopeTree::isIdInCurrentJSScopes(const QString &id) const { auto jsScope = this; while (jsScope) { - if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_currentScopeJSIdentifiers.contains(id)) + if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_jsIdentifiers.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 +bool ScopeTree::isIdInjectedFromSignal(const QString &id) const { - auto qmlScope = this; - while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) { - qmlScope = qmlScope->m_parentScope; - } - return qmlScope; + return currentQMLScope()->m_injectedSignalIdentifiers.contains(id); } -ScopeTree *ScopeTree::getCurrentQMLScope() +const ScopeTree *ScopeTree::currentQMLScope() const { auto qmlScope = this; - while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) { + while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) qmlScope = qmlScope->m_parentScope; - } return qmlScope; } @@ -268,30 +268,37 @@ void ScopeTree::printContext(ColorOutput &colorOut, const QString &code, const QQmlJS::AST::SourceLocation &location) const { IssueLocationWithContext issueLocationWithContext {code, location}; - colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); - colorOut.write(issueLocationWithContext.issueText.toString(), Error); - colorOut.write(issueLocationWithContext.afterText.toString() + QLatin1Char('\n'), Normal); - int tabCount = issueLocationWithContext.beforeText.count(QLatin1Char('\t')); - colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText.length() - tabCount) + 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); } -ScopeType ScopeTree::scopeType() {return m_scopeType;} +void ScopeTree::addExport(const QString &name, const QString &package, + const ComponentVersion &version) +{ + m_exports.append(Export(package, name, version, 0)); +} -void ScopeTree::addMethod(LanguageUtils::FakeMetaMethod method) +void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) { - m_methods.insert(method.methodName(), method); + m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision); } -void ScopeTree::addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject) +ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version, + int metaObjectRevision) : + m_package(std::move(package)), + m_type(std::move(type)), + m_version(version), + m_metaObjectRevision(metaObjectRevision) { - if (metaObject) { - auto methodCount = metaObject->methodCount(); - for (auto i = 0; i < methodCount; ++i) { - auto method = metaObject->method(i); - this->addMethod(method); - } - } +} + +bool ScopeTree::Export::isValid() const +{ + return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); } diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index 52cdc45e96..4b0d4a9539 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -29,15 +29,28 @@ #ifndef SCOPETREE_H #define SCOPETREE_H -#include "fakemetaobject.h" -#include "private/qqmljsast_p.h" -#include "private/qqmljssourcelocation_p.h" +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. -#include -#include -#include +#include "metatypes.h" +#include "componentversion.h" -enum MessageColors{ +#include +#include + +#include +#include +#include + +enum MessageColors +{ Error, Warning, Info, @@ -54,58 +67,137 @@ enum class ScopeType struct MethodUsage { - LanguageUtils::FakeMetaMethod method; + MetaMethod method; QQmlJS::AST::SourceLocation loc; bool hasMultilineHandlerBody; }; class ColorOutput; - -class ScopeTree { +class ScopeTree +{ + Q_DISABLE_COPY_MOVE(ScopeTree) public: - ScopeTree(ScopeType type, QString name="", ScopeTree* parentScope=nullptr); - ~ScopeTree() {qDeleteAll(m_childScopes);} + using Ptr = QSharedPointer; + using ConstPtr = QSharedPointer; + + class Export { + public: + Export() = default; + Export(QString package, QString type, const ComponentVersion &version, + int metaObjectRevision); + + bool isValid() const; + + int metaObjectRevision() const { return m_metaObjectRevision; } + void setMetaObjectRevision(int metaObjectRevision) + { + m_metaObjectRevision = metaObjectRevision; + } - ScopeTree* createNewChildScope(ScopeType type, QString name); - ScopeTree* parentScope(); + QString package() const { return m_package; } + QString type() const { return m_type; } - void insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope); - void insertQMLIdentifier(QString id); - void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody); - void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding + private: + QString m_package; + QString m_type; + ComponentVersion m_version; + int m_metaObjectRevision = 0; + }; + + ScopeTree(ScopeType type, QString name="", ScopeTree *parentScope=nullptr); + ~ScopeTree() { qDeleteAll(m_childScopes); } + + ScopeTree *createNewChildScope(ScopeType type, QString name); + ScopeTree *parentScope() { return m_parentScope; } + + void insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope); + void insertSignalIdentifier(const QString &id, const MetaMethod &method, + const QQmlJS::AST::SourceLocation &loc, bool hasMultilineHandlerBody); + // inserts property as qml identifier as well as the corresponding + void insertPropertyIdentifier(const MetaProperty &prop); void addUnmatchedSignalHandler(const QString &handler, const QQmlJS::AST::SourceLocation &location); - bool isIdInCurrentScope(QString const &id) const; - void addIdToAccssedIfNotInParentScopes(QPair const& id_loc_pair, const QSet& unknownImports); + bool isIdInCurrentScope(const QString &id) const; + void addIdToAccssedIfNotInParentScopes( + const QPair &idLocationPair, + const QSet &unknownImports); bool isVisualRootScope() const; - QString name() const; + QString name() const { return m_name; } + + bool recheckIdentifiers( + const QString &code, const QHash &qmlIDs, + const ScopeTree *root, const QString &rootId, ColorOutput &colorOut) const; + + ScopeType scopeType() const { return m_scopeType; } + + void addMethods(const QHash &methods) { m_methods.unite(methods); } + void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); } + QHash methods() const { return m_methods; } + + void addEnum(const MetaEnum &fakeEnum) { m_enums.insert(fakeEnum.name(), fakeEnum); } + QHash enums() const { return m_enums; } - bool recheckIdentifiers(const QString &code, const QHash& qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const; - ScopeType scopeType(); - void addMethod(LanguageUtils::FakeMetaMethod); - void addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject); - QMapconst & methods() const; + QString className() const { return m_className; } + void setClassName(const QString &name) { m_className = name; } + + void addExport(const QString &name, const QString &package, const ComponentVersion &version); + void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision); + QList exports() const { return m_exports; } + + void setSuperclassName(const QString &superclass) { m_superName = superclass; } + QString superclassName() const { return m_superName; } + + void addProperty(const MetaProperty &prop) { m_properties.insert(prop.name(), prop); } + QHash properties() const { return m_properties; } + + QString defaultPropertyName() const { return m_defaultPropertyName; } + void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; } + + QString attachedTypeName() const { return m_attachedTypeName; } + void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; } + + bool isSingleton() const { return m_isSingleton; } + bool isCreatable() const { return m_isCreatable; } + bool isComposite() const { return m_isComposite; } + void setIsSingleton(bool value) { m_isSingleton = value; } + void setIsCreatable(bool value) { m_isCreatable = value; } + void setIsComposite(bool value) { m_isSingleton = value; } private: - QSet m_currentScopeJSIdentifiers; - QSet m_currentScopeQMLIdentifiers; + QSet m_jsIdentifiers; QMultiHash m_injectedSignalIdentifiers; - QMap m_methods; + + QHash m_methods; + QHash m_properties; + QHash m_enums; + QVector> m_accessedIdentifiers; - QVector m_childScopes; - ScopeTree *m_parentScope; - QString m_name; - ScopeType m_scopeType; QVector> m_unmatchedSignalHandlers; - bool isIdInCurrentQMlScopes(QString id) const; - bool isIdInCurrentJSScopes(QString id) const; - bool isIdInjectedFromSignal(QString id) const; - const ScopeTree* getCurrentQMLScope() const; - ScopeTree* getCurrentQMLScope(); - void printContext(ColorOutput& colorOut, const QString &code, + QVector m_childScopes; + ScopeTree *m_parentScope = nullptr; + + QString m_name; + QString m_className; + QString m_superName; + + ScopeType m_scopeType = ScopeType::QMLScope; + QList m_exports; + + QString m_defaultPropertyName; + QString m_attachedTypeName; + bool m_isSingleton = false; + bool m_isCreatable = true; + bool m_isComposite = false; + + bool isIdInCurrentQMlScopes(const QString &id) const; + bool isIdInCurrentJSScopes(const QString &id) const; + bool isIdInjectedFromSignal(const QString &id) const; + const ScopeTree *currentQMLScope() const; + void printContext(ColorOutput &colorOut, const QString &code, const QQmlJS::AST::SourceLocation &location) const; }; + #endif // SCOPETREE_H diff --git a/tools/qmllint/typedescriptionreader.cpp b/tools/qmllint/typedescriptionreader.cpp new file mode 100644 index 0000000000..4f0cc88800 --- /dev/null +++ b/tools/qmllint/typedescriptionreader.cpp @@ -0,0 +1,698 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "typedescriptionreader.h" + +#include +#include +#include + +#include + +using namespace QQmlJS; +using namespace QQmlJS::AST; + +QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + +bool TypeDescriptionReader::operator()( + QHash *objects, + QList *moduleApis, + QStringList *dependencies) +{ + Engine engine; + + Lexer lexer(&engine); + Parser parser(&engine); + + lexer.setCode(m_source, /*lineno = */ 1, /*qmlMode = */true); + + if (!parser.parse()) { + m_errorMessage = QString::fromLatin1("%1:%2: %3").arg( + QString::number(parser.errorLineNumber()), + QString::number(parser.errorColumnNumber()), + parser.errorMessage()); + return false; + } + + m_objects = objects; + m_moduleApis = moduleApis; + m_dependencies = dependencies; + readDocument(parser.ast()); + + return m_errorMessage.isEmpty(); +} + +void TypeDescriptionReader::readDocument(UiProgram *ast) +{ + if (!ast) { + addError(SourceLocation(), tr("Could not parse document.")); + return; + } + + if (!ast->headers || ast->headers->next || !cast(ast->headers->headerItem)) { + addError(SourceLocation(), tr("Expected a single import.")); + return; + } + + auto *import = cast(ast->headers->headerItem); + if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) { + addError(import->importToken, tr("Expected import of QtQuick.tooling.")); + return; + } + + if (!import->version) { + addError(import->firstSourceLocation(), tr("Import statement without version.")); + return; + } + + if (import->version->majorVersion != 1) { + addError(import->version->firstSourceLocation(), + tr("Major version different from 1 not supported.")); + return; + } + + if (!ast->members || !ast->members->member || ast->members->next) { + addError(SourceLocation(), tr("Expected document to contain a single object definition.")); + return; + } + + auto *module = cast(ast->members->member); + if (!module) { + addError(SourceLocation(), tr("Expected document to contain a single object definition.")); + return; + } + + if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) { + addError(SourceLocation(), tr("Expected document to contain a Module {} member.")); + return; + } + + readModule(module); +} + +void TypeDescriptionReader::readModule(UiObjectDefinition *ast) +{ + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *component = cast(member); + + auto *script = cast(member); + if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) { + readDependencies(script); + continue; + } + + QString typeName; + if (component) + typeName = toString(component->qualifiedTypeNameId); + + if (!component || (typeName != QLatin1String("Component") + && 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) +{ + m_errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( + QDir::toNativeSeparators(m_fileName), + QString::number(loc.startLine), + QString::number(loc.startColumn), + message); +} + +void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message) +{ + m_warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( + QDir::toNativeSeparators(m_fileName), + QString::number(loc.startLine), + QString::number(loc.startColumn), + message); +} + +void TypeDescriptionReader::readDependencies(UiScriptBinding *ast) +{ + auto *stmt = cast(ast->statement); + if (!stmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions")); + return; + } + auto *exp = cast(stmt->expression); + if (!exp) { + addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions")); + return; + } + for (PatternElementList *l = exp->elements; l; l = l->next) { + auto *str = cast(l->element->initializer); + *m_dependencies << str->value.toString(); + } +} + +void TypeDescriptionReader::readComponent(UiObjectDefinition *ast) +{ + ScopeTree::Ptr scope(new ScopeTree(ScopeType::QMLScope)); + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *component = cast(member); + auto *script = cast(member); + if (component) { + QString name = toString(component->qualifiedTypeNameId); + if (name == QLatin1String("Property")) + readProperty(component, scope); + else if (name == QLatin1String("Method") || name == QLatin1String("Signal")) + readSignalOrMethod(component, name == QLatin1String("Method"), scope); + else if (name == QLatin1String("Enum")) + readEnum(component, scope); + else + addWarning(component->firstSourceLocation(), + tr("Expected only Property, Method, Signal and Enum object definitions, " + "not \"%1\".").arg(name)); + } else if (script) { + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + scope->setClassName(readStringBinding(script)); + } else if (name == QLatin1String("prototype")) { + scope->setSuperclassName(readStringBinding(script)); + } else if (name == QLatin1String("defaultProperty")) { + scope->setDefaultPropertyName(readStringBinding(script)); + } else if (name == QLatin1String("exports")) { + readExports(script, scope); + } else if (name == QLatin1String("exportMetaObjectRevisions")) { + readMetaObjectRevisions(script, scope); + } else if (name == QLatin1String("attachedType")) { + scope->setAttachedTypeName(readStringBinding(script)); + } else if (name == QLatin1String("isSingleton")) { + scope->setIsSingleton(readBoolBinding(script)); + } else if (name == QLatin1String("isCreatable")) { + scope->setIsCreatable(readBoolBinding(script)); + } else if (name == QLatin1String("isComposite")) { + scope->setIsComposite(readBoolBinding(script)); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name, prototype, defaultProperty, attachedType, " + "exports, isSingleton, isCreatable, isComposite and " + "exportMetaObjectRevisions script bindings, not \"%1\".").arg(name)); + } + } else { + addWarning(member->firstSourceLocation(), + tr("Expected only script bindings and object definitions.")); + } + } + + if (scope->className().isEmpty()) { + addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding.")); + return; + } + + // ### add implicit export into the package of c++ types + scope->addExport(scope->className(), QStringLiteral(""), ComponentVersion()); + m_objects->insert(scope->className(), scope); +} + +void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast) +{ + ModuleApiInfo apiInfo; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(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 (m_moduleApis) + m_moduleApis->append(apiInfo); +} + +void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, + const ScopeTree::Ptr &scope) +{ + MetaMethod metaMethod; + // ### confusion between Method and Slot. Method should be removed. + if (isMethod) + metaMethod.setMethodType(MetaMethod::Slot); + else + metaMethod.setMethodType(MetaMethod::Signal); + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *component = cast(member); + auto *script = cast(member); + if (component) { + QString name = toString(component->qualifiedTypeNameId); + if (name == QLatin1String("Parameter")) { + readParameter(component, &metaMethod); + } else { + addWarning(component->firstSourceLocation(), + tr("Expected only Parameter object definitions.")); + } + } else if (script) { + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + metaMethod.setMethodName(readStringBinding(script)); + } else if (name == QLatin1String("type")) { + metaMethod.setReturnType(readStringBinding(script)); + } else if (name == QLatin1String("revision")) { + metaMethod.setRevision(readIntBinding(script)); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and type script bindings.")); + } + } else { + addWarning(member->firstSourceLocation(), + tr("Expected only script bindings and object definitions.")); + } + } + + if (metaMethod.methodName().isEmpty()) { + addError(ast->firstSourceLocation(), + tr("Method or signal is missing a name script binding.")); + return; + } + + scope->addMethod(metaMethod); +} + +void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) +{ + QString name; + QString type; + bool isPointer = false; + bool isReadonly = false; + bool isList = false; + int revision = 0; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + QString id = toString(script->qualifiedId); + if (id == QLatin1String("name")) { + name = readStringBinding(script); + } else if (id == QLatin1String("type")) { + type = readStringBinding(script); + } else if (id == QLatin1String("isPointer")) { + isPointer = readBoolBinding(script); + } else if (id == QLatin1String("isReadonly")) { + isReadonly = readBoolBinding(script); + } else if (id == QLatin1String("isList")) { + isList = readBoolBinding(script); + } else if (id == QLatin1String("revision")) { + revision = readIntBinding(script); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only type, name, revision, isPointer, isReadonly and" + " isList script bindings.")); + } + } + + if (name.isEmpty() || type.isEmpty()) { + addError(ast->firstSourceLocation(), + tr("Property object is missing a name or type script binding.")); + return; + } + + scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, revision)); +} + +void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) +{ + MetaEnum metaEnum; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + metaEnum.setName(readStringBinding(script)); + } else if (name == QLatin1String("values")) { + readEnumValues(script, &metaEnum); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and values script bindings.")); + } + } + + scope->addEnum(metaEnum); +} + +void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, MetaMethod *metaMethod) +{ + QString name; + QString type; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + auto *script = cast(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + const QString id = toString(script->qualifiedId); + if (id == QLatin1String("name")) { + name = readStringBinding(script); + } else if (id == QLatin1String("type")) { + type = readStringBinding(script); + } else if (id == QLatin1String("isPointer")) { + // ### unhandled + } else if (id == QLatin1String("isReadonly")) { + // ### unhandled + } else if (id == QLatin1String("isList")) { + // ### unhandled + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name and type script bindings.")); + } + } + + metaMethod->addParameter(name, type); +} + +QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected string after colon.")); + return QString(); + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected string after colon.")); + return QString(); + } + + auto *stringLit = cast(expStmt->expression); + if (!stringLit) { + addError(expStmt->firstSourceLocation(), tr("Expected string after colon.")); + return QString(); + } + + return stringLit->value.toString(); +} + +bool TypeDescriptionReader::readBoolBinding(UiScriptBinding *ast) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected boolean after colon.")); + return false; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon.")); + return false; + } + + auto *trueLit = cast(expStmt->expression); + auto *falseLit = cast(expStmt->expression); + if (!trueLit && !falseLit) { + addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon.")); + return false; + } + + return trueLit; +} + +double TypeDescriptionReader::readNumericBinding(UiScriptBinding *ast) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected numeric literal after colon.")); + return 0; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected numeric literal after colon.")); + return 0; + } + + auto *numericLit = cast(expStmt->expression); + if (!numericLit) { + addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return 0; + } + + return numericLit->value; +} + +ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast) +{ + ComponentVersion invalidVersion; + + if (!ast || !ast->statement) { + addError((ast ? ast->colonToken : SourceLocation()), + tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + auto *numericLit = cast(expStmt->expression); + if (!numericLit) { + addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + return ComponentVersion(m_source.mid(numericLit->literalToken.begin(), + numericLit->literalToken.length)); +} + +int TypeDescriptionReader::readIntBinding(UiScriptBinding *ast) +{ + double v = readNumericBinding(ast); + int i = static_cast(v); + + if (i != v) { + addError(ast->firstSourceLocation(), tr("Expected integer after colon.")); + return 0; + } + + return i; +} + +void TypeDescriptionReader::readExports(UiScriptBinding *ast, const ScopeTree::Ptr &scope) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of strings after colon.")); + return; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected array of strings after colon.")); + return; + } + + auto *arrayLit = cast(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon.")); + return; + } + + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + auto *stringLit = cast(it->element->initializer); + if (!stringLit) { + addError(arrayLit->firstSourceLocation(), + tr("Expected array literal with only string literal members.")); + return; + } + QString exp = stringLit->value.toString(); + int slashIdx = exp.indexOf(QLatin1Char('/')); + int spaceIdx = exp.indexOf(QLatin1Char(' ')); + ComponentVersion version(exp.mid(spaceIdx + 1)); + + if (spaceIdx == -1 || !version.isValid()) { + addError(stringLit->firstSourceLocation(), + tr("Expected string literal to contain 'Package/Name major.minor' " + "or 'Name major.minor'.")); + continue; + } + QString package; + if (slashIdx != -1) + package = exp.left(slashIdx); + QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1)); + + // ### relocatable exports where package is empty? + scope->addExport(name, package, version); + } +} + +void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, + const ScopeTree::Ptr &scope) +{ + Q_ASSERT(ast); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of numbers after colon.")); + return; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), + tr("Expected array of numbers after colon.")); + return; + } + + auto *arrayLit = cast(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon.")); + return; + } + + int exportIndex = 0; + const int exportCount = scope->exports().size(); + for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) { + auto *numberLit = cast(it->element->initializer); + if (!numberLit) { + addError(arrayLit->firstSourceLocation(), + tr("Expected array literal with only number literal members.")); + return; + } + + if (exportIndex >= exportCount) { + addError(numberLit->firstSourceLocation(), + tr("Meta object revision without matching export.")); + return; + } + + const double v = numberLit->value; + const int metaObjectRevision = static_cast(v); + if (metaObjectRevision != v) { + addError(numberLit->firstSourceLocation(), tr("Expected integer.")); + return; + } + + scope->setExportMetaObjectRevision(exportIndex, metaObjectRevision); + } +} + +void TypeDescriptionReader::readEnumValues(UiScriptBinding *ast, MetaEnum *metaEnum) +{ + if (!ast) + return; + if (!ast->statement) { + addError(ast->colonToken, tr("Expected object literal after colon.")); + return; + } + + auto *expStmt = cast(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon.")); + return; + } + + if (auto *objectLit = cast(expStmt->expression)) { + for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { + if (PatternProperty *assignement = it->property) { + if (auto *name = cast(assignement->name)) { + metaEnum->addKey(name->id.toString()); + continue; + } + } + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); + } + } else if (auto *arrayLit = cast(expStmt->expression)) { + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + if (PatternElement *element = it->element) { + if (auto *name = cast(element->initializer)) { + metaEnum->addKey(name->value.toString()); + continue; + } + } + addError(it->firstSourceLocation(), tr("Expected strings as enum keys.")); + } + } else { + addError(ast->statement->firstSourceLocation(), + tr("Expected either array or object literal as enum definition.")); + } +} diff --git a/tools/qmllint/typedescriptionreader.h b/tools/qmllint/typedescriptionreader.h new file mode 100644 index 0000000000..5fcbe3abc9 --- /dev/null +++ b/tools/qmllint/typedescriptionreader.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 TYPEDESCRIPTIONREADER_H +#define TYPEDESCRIPTIONREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree.h" + +#include + +// for Q_DECLARE_TR_FUNCTIONS +#include + +struct ModuleApiInfo +{ + QString uri; + ComponentVersion version; + QString cppName; +}; + +class TypeDescriptionReader +{ + Q_DECLARE_TR_FUNCTIONS(TypeDescriptionReader) +public: + TypeDescriptionReader() = default; + explicit TypeDescriptionReader(QString fileName, QString data) + : m_fileName(std::move(fileName)), m_source(std::move(data)) {} + + bool operator()( + QHash *objects, + QList *moduleApis, + QStringList *dependencies); + + QString errorMessage() const { return m_errorMessage; } + QString warningMessage() const { return m_warningMessage; } + +private: + void readDocument(QQmlJS::AST::UiProgram *ast); + void readModule(QQmlJS::AST::UiObjectDefinition *ast); + void readDependencies(QQmlJS::AST::UiScriptBinding *ast); + void readComponent(QQmlJS::AST::UiObjectDefinition *ast); + void readModuleApi(QQmlJS::AST::UiObjectDefinition *ast); + void readSignalOrMethod(QQmlJS::AST::UiObjectDefinition *ast, bool isMethod, + const ScopeTree::Ptr &scope); + void readProperty(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); + void readEnum(QQmlJS::AST::UiObjectDefinition *ast, const ScopeTree::Ptr &scope); + void readParameter(QQmlJS::AST::UiObjectDefinition *ast, MetaMethod *metaMethod); + + QString readStringBinding(QQmlJS::AST::UiScriptBinding *ast); + bool readBoolBinding(QQmlJS::AST::UiScriptBinding *ast); + double readNumericBinding(QQmlJS::AST::UiScriptBinding *ast); + ComponentVersion readNumericVersionBinding(QQmlJS::AST::UiScriptBinding *ast); + int readIntBinding(QQmlJS::AST::UiScriptBinding *ast); + void readExports(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); + void readMetaObjectRevisions(QQmlJS::AST::UiScriptBinding *ast, const ScopeTree::Ptr &scope); + void readEnumValues(QQmlJS::AST::UiScriptBinding *ast, MetaEnum *metaEnum); + + void addError(const QQmlJS::AST::SourceLocation &loc, const QString &message); + void addWarning(const QQmlJS::AST::SourceLocation &loc, const QString &message); + + QString m_fileName; + QString m_source; + QString m_errorMessage; + QString m_warningMessage; + QHash *m_objects = nullptr; + QList *m_moduleApis = nullptr; + QStringList *m_dependencies = nullptr; +}; + +#endif // TYPEDESCRIPTIONREADER_H -- cgit v1.2.3 From 6b1f42c72f060010f3c73f4502dcd10647f205c1 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 13 Nov 2019 17:50:02 +0100 Subject: qmllint: Remove special cases for Component, QtObject, and Connections The plugins.qmltypes for QtQml have proper descriptions for those. They do have some interesting properties, after all. Change-Id: I7f0922dd14c66c64fe88b55087fd55cb992d33db Reviewed-by: Simon Hausmann Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index ab7ff80609..6a4071acc7 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -431,15 +431,6 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) // using an empty ScopeTree m_exportedName2Scope[QFileInfo { m_filePath }.baseName()] = {}; - // add QML builtins - m_exportedName2Scope["QtObject"] = {}; // QtObject contains nothing of interest - - ScopeTree *scope = new ScopeTree(ScopeType::QMLScope); - scope->addProperty(MetaProperty {"enabled", "bool", false, false, false, 0}); - scope->addProperty(MetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0}); - scope->addProperty(MetaProperty {"target", "QObject", false, false, false, 0}); - m_exportedName2Scope["Connections"] = ScopeTree::ConstPtr { scope }; - importDirectory(".", QString()); return true; } @@ -798,9 +789,6 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) m_currentScope->addProperty(prop); enterEnvironment(ScopeType::QMLScope, name); - // there is no typeinfo for Component and QtObject, but they also have no interesting properties - if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) - return true; importExportedNames(prefix, name); return true; } @@ -826,10 +814,6 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) if (name.isLower()) return false; // Ignore grouped properties for now - // there is no typeinfo for Component - if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) - return true; - importExportedNames(prefix, name); if (name.endsWith("Connections")) { QString target; -- cgit v1.2.3 From 355fd4bf5cd0a83b52b37c2a905cee867f9af4d6 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 14 Nov 2019 17:08:15 +0100 Subject: qmllint: Read app.qmltypes files for extra type resolution Change-Id: I7f19b39dd65063ae26b93bf1b40b0be0b0e15c6d Reviewed-by: Simon Hausmann --- tools/qmllint/findunqualified.cpp | 172 ++++++++++++++++++++++---------------- tools/qmllint/findunqualified.h | 15 +++- 2 files changed, 113 insertions(+), 74 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 6a4071acc7..6274238f96 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -69,8 +69,7 @@ void FindUnqualifiedIDVisitor::leaveEnvironment() enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; -QStringList completeImportPaths(const QString &uri, const QStringList &basePaths, - int vmaj, int vmin) +QStringList completeImportPaths(const QString &uri, const QString &basePath, int vmaj, int vmin) { static const QLatin1Char Slash('/'); static const QLatin1Char Backslash('\\'); @@ -79,7 +78,7 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths QStringList qmlDirPathsPaths; // fully & partially versioned parts + 1 unversioned for each base path - qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1)); + qmlDirPathsPaths.reserve(2 * parts.count() + 1); auto versionString = [](int vmaj, int vmin, ImportVersion version) { @@ -107,96 +106,93 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths for (int version = FullyVersioned; version <= BasePath; ++version) { const QString ver = versionString(vmaj, vmin, static_cast(version)); - for (const QString &path : basePaths) { - QString dir = path; - if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) - dir += Slash; + QString dir = basePath; + if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) + dir += Slash; - if (version == BasePath) { - qmlDirPathsPaths += dir; - } else { - // append to the end - qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver; - } + if (version == BasePath) { + qmlDirPathsPaths += dir; + } else { + // append to the end + qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver; + } - 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); - } + 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); } } } return qmlDirPathsPaths; } -void FindUnqualifiedIDVisitor::importHelper(QString id, const QString &prefix, int major, int minor) -{ - QPair importId { id, prefix }; - if (m_alreadySeenImports.contains(importId)) - return; +static const QLatin1String SlashQmldir = QLatin1String("/qmldir"); +static const QLatin1String SlashAppDotQmltypes = QLatin1String("/app.qmltypes"); +static const QLatin1String SlashLibDotQmltypes = QLatin1String("/lib.qmltypes"); +static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes"); - m_alreadySeenImports.insert(importId); +void FindUnqualifiedIDVisitor::readQmltypes(const QString &filename, + FindUnqualifiedIDVisitor::Import &result) +{ + auto reader = createQmltypesReaderForFile(filename); + auto succ = reader(&result.objects, &result.moduleApis, &result.dependencies); + if (!succ) + m_colorOut.writeUncolored(reader.errorMessage()); +} - id = id.replace(QLatin1String("/"), QLatin1String(".")); - auto qmltypesPaths = completeImportPaths(id, m_qmltypeDirs, major, minor); +FindUnqualifiedIDVisitor::Import FindUnqualifiedIDVisitor::readQmldir(const QString &path) +{ + Import result; + auto reader = createQmldirParserForFile(path + SlashQmldir); + const auto imports = reader.imports(); + for (const QString &import : imports) + result.dependencies.append(import); - QHash objects; - QList moduleApis; - QStringList dependencies; - static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); - static const QLatin1String SlashQmldir("/qmldir"); - for (auto const &qmltypesPath : qmltypesPaths) { - if (QFile::exists(qmltypesPath + SlashQmldir)) { - auto reader = createQmldirParserForFile(qmltypesPath + SlashQmldir); - const auto imports = reader.imports(); - for (const QString &import : imports) - importHelper(import, prefix, major, minor); - - QHash qmlComponents; - const auto components = reader.components(); - for (auto it = components.begin(), end = components.end(); it != end; ++it) { - const QString filePath = qmltypesPath + QLatin1Char('/') + it->fileName; - if (!QFile::exists(filePath)) { - m_colorOut.write(QLatin1String("warning: "), Warning); - m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ") - + qmltypesPath + SlashQmldir - + QLatin1String(" but does not exist.\n")); - continue; - } + QHash qmlComponents; + const auto components = reader.components(); + for (auto it = components.begin(), end = components.end(); it != end; ++it) { + const QString filePath = path + QLatin1Char('/') + it->fileName; + if (!QFile::exists(filePath)) { + m_colorOut.write(QLatin1String("warning: "), Warning); + m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ") + + path + SlashQmldir + + QLatin1String(" but does not exist.\n")); + continue; + } - auto mo = qmlComponents.find(it.key()); - if (mo == qmlComponents.end()) - mo = qmlComponents.insert(it.key(), localQmlFile2ScopeTree(filePath)); + auto mo = qmlComponents.find(it.key()); + if (mo == qmlComponents.end()) + mo = qmlComponents.insert(it.key(), localQmlFile2ScopeTree(filePath)); - (*mo)->addExport( - it.key(), reader.typeNamespace(), - ComponentVersion(it->majorVersion, it->minorVersion)); - } - for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) { - objects.insert(it.key(), - QSharedPointer(it.value())); - } - } - if (QFile::exists(qmltypesPath + SlashPluginsDotQmltypes)) { - auto reader = createQmltypesReaderForFile(qmltypesPath + SlashPluginsDotQmltypes); - auto succ = reader(&objects, &moduleApis, &dependencies); - if (!succ) - m_colorOut.writeUncolored(reader.errorMessage()); - } + (*mo)->addExport( + it.key(), reader.typeNamespace(), + ComponentVersion(it->majorVersion, it->minorVersion)); } - for (auto const &dependency : qAsConst(dependencies)) { + for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) + result.objects.insert( it.key(), ScopeTree::ConstPtr(it.value())); + + if (!reader.plugins().isEmpty() && QFile::exists(path + SlashPluginsDotQmltypes)) + readQmltypes(path + SlashPluginsDotQmltypes, result); + + return result; +} + +void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUnqualifiedIDVisitor::Import &import) +{ + for (auto const &dependency : qAsConst(import.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(); + for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { + const auto &val = it.value(); m_exportedName2Scope[prefix + val->className()] = val; const auto exports = val->exports(); @@ -209,6 +205,38 @@ void FindUnqualifiedIDVisitor::importHelper(QString id, const QString &prefix, i } } +void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int major, int minor) +{ + id = id.replace(QLatin1String("/"), QLatin1String(".")); + QPair importId { id, prefix }; + if (m_alreadySeenImports.contains(importId)) + return; + m_alreadySeenImports.insert(importId); + + for (const QString &qmltypeDir : m_qmltypeDirs) { + auto qmltypesPaths = completeImportPaths(id, qmltypeDir, major, minor); + + for (auto const &qmltypesPath : qmltypesPaths) { + if (QFile::exists(qmltypesPath + SlashQmldir)) { + processImport(prefix, readQmldir(qmltypesPath)); + + // break so that we don't import unversioned qml components + // in addition to versioned ones + break; + } + + Import result; + if (QFile::exists(qmltypesPath + SlashAppDotQmltypes)) + readQmltypes(qmltypesPath + SlashAppDotQmltypes, result); + else if (QFile::exists(qmltypesPath + SlashLibDotQmltypes)) + readQmltypes(qmltypesPath + SlashLibDotQmltypes, result); + else + continue; + processImport(prefix, result); + } + } +} + ScopeTree *FindUnqualifiedIDVisitor::localQmlFile2ScopeTree(const QString &filePath) { using namespace QQmlJS::AST; diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 8b79918d90..a8fa435464 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -58,6 +58,12 @@ public: bool check(); private: + struct Import { + QHash objects; + QList moduleApis; + QStringList dependencies; + }; + QScopedPointer m_rootScope; ScopeTree *m_currentScope = nullptr; QHash m_exportedName2Scope; @@ -82,8 +88,13 @@ private: void enterEnvironment(ScopeType type, QString name); void leaveEnvironment(); - void importHelper(QString id, const QString &prefix, int major, int minor); - ScopeTree* localQmlFile2ScopeTree(const QString &filePath); + void importHelper(QString id, QString prefix, int major, int minor); + + void readQmltypes(const QString &filename, Import &result); + Import readQmldir(const QString &dirname); + void processImport(const QString &prefix, const Import &import); + + ScopeTree *localQmlFile2ScopeTree(const QString &filePath); void importDirectory(const QString &directory, const QString &prefix); void importExportedNames(const QStringRef &prefix, QString name); -- cgit v1.2.3 From d07a5221ce0a138743341e2edeaba6f8488f42f7 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 11 Dec 2019 15:31:55 +0100 Subject: qmllint: Handle unversioned import statements in qmldir files The import statements in qmldir files are not required to have a version attached to them anymore. Change-Id: I925d5f965c73685874c23510cf2e569d1528d8c1 Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 23 +++++++++++++++++------ tools/qmllint/findunqualified.h | 3 ++- 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 6274238f96..af2bf1ae7e 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -103,7 +103,10 @@ QStringList completeImportPaths(const QString &uri, const QString &basePath, int return str; }; - for (int version = FullyVersioned; version <= BasePath; ++version) { + const ImportVersion initial = (vmin >= 0) + ? FullyVersioned + : (vmaj >= 0 ? PartiallyVersioned : Unversioned); + for (int version = initial; version <= BasePath; ++version) { const QString ver = versionString(vmaj, vmin, static_cast(version)); QString dir = basePath; @@ -185,9 +188,16 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn for (auto const &dependency : qAsConst(import.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); + if (split.length() > 1) { + const auto version = split.at(1).split('.'); + importHelper(id, QString(), + version.at(0).toInt(), + version.length() > 1 ? version.at(1).toInt() : -1); + } else { + importHelper(id, QString(), -1, -1); + } + + } // add objects @@ -205,9 +215,10 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn } } -void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int major, int minor) +void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString &prefix, + int major, int minor) { - id = id.replace(QLatin1String("/"), QLatin1String(".")); + const QString id = QString(module).replace(QLatin1Char('/'), QLatin1Char('.')); QPair importId { id, prefix }; if (m_alreadySeenImports.contains(importId)) return; diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index a8fa435464..483a6da039 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -88,7 +88,8 @@ private: void enterEnvironment(ScopeType type, QString name); void leaveEnvironment(); - void importHelper(QString id, QString prefix, int major, int minor); + void importHelper(const QString &module, const QString &prefix = QString(), + int major = -1, int minor = -1); void readQmltypes(const QString &filename, Import &result); Import readQmldir(const QString &dirname); -- cgit v1.2.3 From 4f6815b29003e140be5ac5763429a4bb256f5b9f Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 13 Dec 2019 09:06:05 +0100 Subject: qmllint: Avoid QHash::unite() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we don't have QHash::insert(QHash), yet, we have to iterate for now. Change-Id: I3deaf8c0fb53446bb9f125786d20ed89abe85c42 Reviewed-by: Fabian Kosmale Reviewed-by: MÃ¥rten Nordheim --- tools/qmllint/scopetree.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index 4b0d4a9539..d6f0a2c171 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -132,7 +132,11 @@ public: ScopeType scopeType() const { return m_scopeType; } - void addMethods(const QHash &methods) { m_methods.unite(methods); } + void addMethods(const QHash &methods) + { + for (auto it = methods.begin(), end = methods.end(); it != end; ++it) + m_methods.insert(it.key(), it.value()); + } void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); } QHash methods() const { return m_methods; } -- cgit v1.2.3 From 8200c7500f5d1b7bf83742df903fea3f52e50fbf Mon Sep 17 00:00:00 2001 From: Liang Qi Date: Mon, 16 Dec 2019 12:35:38 +0100 Subject: qmllint: Use QHash::insert(QHash) This amends 4f6815b29003e140be5ac5763429a4bb256f5b9f. Change-Id: Icbe08d2c636969b40e7092b18c4bc2d21476944a Reviewed-by: Ulf Hermann --- tools/qmllint/scopetree.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index d6f0a2c171..b7619c4352 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -132,11 +132,7 @@ public: ScopeType scopeType() const { return m_scopeType; } - void addMethods(const QHash &methods) - { - for (auto it = methods.begin(), end = methods.end(); it != end; ++it) - m_methods.insert(it.key(), it.value()); - } + void addMethods(const QHash &methods) { m_methods.insert(methods); } void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); } QHash methods() const { return m_methods; } -- cgit v1.2.3 From cc759804aa4156eb75094345dbfe21597b2fe3b8 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 17 Dec 2019 09:44:10 +0100 Subject: Avoid deprecated non-q-prefixed hex() function Use Qt::hex() instead. Change-Id: I46bc2f2006ce860cd938a86432783011365c8164 Reviewed-by: Liang Qi --- tools/qmlcachegen/qmlcachegen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index 920f451b84..b1670c940d 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -374,7 +374,7 @@ static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFil { QTextStream stream(&hexifiedData); const uchar *end = begin + size; - stream << hex; + stream << Qt::hex; int col = 0; for (const uchar *data = begin; data < end; ++data, ++col) { if (data > begin) -- cgit v1.2.3 From 155a2e0d8d1d9c07fa22f523d84e3d583697aff4 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Mon, 4 Nov 2019 11:20:10 +0100 Subject: Fix usage of qtquickcompiler.prf after lrelease.prf Consider a project that sets CONFIG += lrelease embed_translations qtquickcompiler qtquickcompiler.prf is loaded first and loads resources.prf. Then lrelease.prf is loaded and extends RESOURCES. The latter changes are never picked up, because resources.prf was already loaded which is recorded in QMAKE_INTERNAL_INCLUDED_FEATURES. Use the newly introduced qtFlattenResources function instead of fully loading resources.prf. Task-number: QTBUG-79672 Change-Id: I1894ede97b4d5de567971a1a8cef407460bba97d Reviewed-by: Kai Koehne --- tools/qmlcachegen/qtquickcompiler.prf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf index 2f98aadefe..77906b19a6 100644 --- a/tools/qmlcachegen/qtquickcompiler.prf +++ b/tools/qmlcachegen/qtquickcompiler.prf @@ -31,7 +31,8 @@ defineTest(qtQuickSkippedResourceFile) { } # Flatten RESOURCES that may contain individual files or objects -load(resources) +load(resources_functions) +qtFlattenResources() NEWRESOURCES = QMLCACHE_RESOURCE_FILES = -- cgit v1.2.3 From 5fb8f1a3283f34482521250d4e44e6bd8dd9323e Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 26 Jun 2019 16:46:23 +0200 Subject: Fix Qt6 build in preparation of qt5 submodule update Fixes the QTextStream usages. Change-Id: I0c009a82fb644a9f3c3d42ec410d18b680977f23 (cherry picked from commit 1c5c5f7aadc2dcc73a21eeb818e95c4e1b7de70f) Reviewed-by: Friedemann Kleint Reviewed-by: Alexandru Croitor --- tools/qmlpreview/qmlpreviewapplication.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/qmlpreview/qmlpreviewapplication.cpp b/tools/qmlpreview/qmlpreviewapplication.cpp index 17017dae77..2568425573 100644 --- a/tools/qmlpreview/qmlpreviewapplication.cpp +++ b/tools/qmlpreview/qmlpreviewapplication.cpp @@ -195,7 +195,7 @@ void QmlPreviewApplication::processFinished() void QmlPreviewApplication::logError(const QString &error) { QTextStream err(stderr); - err << "Error: " << error << endl; + err << "Error: " << error << Qt::endl; } void QmlPreviewApplication::logStatus(const QString &status) @@ -203,7 +203,7 @@ void QmlPreviewApplication::logStatus(const QString &status) if (!m_verbose) return; QTextStream err(stderr); - err << status << endl; + err << status << Qt::endl; } void QmlPreviewApplication::serveRequest(const QString &path) -- cgit v1.2.3 From 73189151b59430671630ae931ac42c0e18cdab55 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Fri, 13 Dec 2019 16:10:46 +0100 Subject: qtdeclarative/tools: Implement qmlformat qmlformat is a tool that formats qml files according to the QML coding conventions (https://doc.qt.io/qt-5/qml-codingconventions.html). Change-Id: I359c4bd3b51f60614535f0e857d7e0b21b1d9033 Reviewed-by: Ulf Hermann --- tools/qmlformat/commentastvisitor.cpp | 270 +++++++++ tools/qmlformat/commentastvisitor.h | 131 +++++ tools/qmlformat/dumpastvisitor.cpp | 927 ++++++++++++++++++++++++++++++ tools/qmlformat/dumpastvisitor.h | 123 ++++ tools/qmlformat/main.cpp | 164 ++++++ tools/qmlformat/qmlformat.pro | 17 + tools/qmlformat/restructureastvisitor.cpp | 178 ++++++ tools/qmlformat/restructureastvisitor.h | 50 ++ tools/tools.pro | 3 +- 9 files changed, 1862 insertions(+), 1 deletion(-) create mode 100644 tools/qmlformat/commentastvisitor.cpp create mode 100644 tools/qmlformat/commentastvisitor.h create mode 100644 tools/qmlformat/dumpastvisitor.cpp create mode 100644 tools/qmlformat/dumpastvisitor.h create mode 100644 tools/qmlformat/main.cpp create mode 100644 tools/qmlformat/qmlformat.pro create mode 100644 tools/qmlformat/restructureastvisitor.cpp create mode 100644 tools/qmlformat/restructureastvisitor.h (limited to 'tools') diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp new file mode 100644 index 0000000000..6d2bf49ddc --- /dev/null +++ b/tools/qmlformat/commentastvisitor.cpp @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** 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 "commentastvisitor.h" + +CommentAstVisitor::CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode) : m_engine(engine) +{ + rootNode->accept(this); + + // Look for complete orphans that have not been attached to *any* node + QVector completeOrphans; + + for (const auto &comment : m_engine->comments()) { + if (isCommentAttached(comment)) + continue; + + bool found_orphan = false; + for (const auto &orphanList : orphanComments().values()) { + for (const auto &orphan : orphanList) { + if (orphan.contains(comment)) { + found_orphan = true; + break; + } + } + + if (found_orphan) + break; + } + + if (found_orphan) + continue; + + completeOrphans.append(Comment(m_engine, Comment::Location::Front, {comment})); + } + + m_orphanComments[nullptr] = completeOrphans; +} + +QList CommentAstVisitor::findCommentsInLine(quint32 line, bool includePrevious) const +{ + QList results; + if (line == 0) + return results; + + for (const auto &location : m_engine->comments()) { + if (location.startLine != line) + continue; + + if (isCommentAttached(location)) + continue; + + results.append(location); + + if (includePrevious) { + // See if we can find any more comments above this one + auto previous = findCommentsInLine(line - 1, true); + + // Iterate it in reverse to restore the correct order + for (auto it = previous.rbegin(); it != previous.rend(); it++) { + results.prepend(*it); + } + } + + break; + } + + return results; +} + +bool CommentAstVisitor::isCommentAttached(const SourceLocation &location) const +{ + for (const auto &value : m_attachedComments.values()) { + if (value.contains(location)) + return true; + } + + for (const auto &value : m_listItemComments.values()) { + if (value.contains(location)) + return true; + } + + // If a comment is already marked as an orphan of a Node that counts as attached too. + for (const auto &orphanList : m_orphanComments.values()) { + for (const auto &value : orphanList) { + if (value.contains(location)) + return true; + } + } + + return false; +} + +Comment CommentAstVisitor::findComment(SourceLocation first, SourceLocation last, + int locations) const +{ + if (locations & Comment::Location::Front) { + quint32 searchAt = first.startLine - 1; + + const auto comments = findCommentsInLine(searchAt, true); + if (!comments.isEmpty()) + return Comment(m_engine, Comment::Location::Front, comments); + } + + if (locations & Comment::Location::Back_Inline) { + quint32 searchAt = last.startLine; + + const auto comments = findCommentsInLine(searchAt); + if (!comments.isEmpty()) + return Comment(m_engine, Comment::Location::Back_Inline, comments); + } + + if (locations & Comment::Location::Back) { + quint32 searchAt = last.startLine + 1; + + const auto comments = findCommentsInLine(searchAt); + if (!comments.isEmpty()) + return Comment(m_engine, Comment::Location::Back, comments); + } + + return Comment(); + +} + +Comment CommentAstVisitor::findComment(Node *node, int locations) const +{ + return findComment(node->firstSourceLocation(), node->lastSourceLocation(), locations); +} + +QVector CommentAstVisitor::findOrphanComments(Node *node) const +{ + QVector comments; + + for (auto &comment : m_engine->comments()) { + if (isCommentAttached(comment)) + continue; + + if (comment.begin() <= node->firstSourceLocation().begin() + || comment.end() > node->lastSourceLocation().end()) { + continue; + } + + comments.append(Comment(m_engine, Comment::Location::Front, {comment})); + } + + return comments; +} + +void CommentAstVisitor::attachComment(Node *node, int locations) +{ + auto comment = findComment(node, locations); + + if (comment.isValid()) + m_attachedComments[node] = comment; +} + +bool CommentAstVisitor::visit(UiScriptBinding *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(StatementList *node) +{ + for (auto *item = node; item != nullptr; item = item->next) + attachComment(item->statement, Comment::Front | Comment::Back_Inline); + return true; +} + +void CommentAstVisitor::endVisit(StatementList *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiObjectBinding *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(UiObjectDefinition *node) +{ + attachComment(node); + return true; +} + +void CommentAstVisitor::endVisit(UiObjectDefinition *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiArrayBinding *node) +{ + attachComment(node); + return true; +} + +void CommentAstVisitor::endVisit(UiArrayBinding *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiEnumDeclaration *node) +{ + attachComment(node); + return true; +} + +void CommentAstVisitor::endVisit(UiEnumDeclaration *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiEnumMemberList *node) +{ + for (auto *item = node; item != nullptr; item = item->next) { + auto comment = findComment(item->memberToken, + item->valueToken.isValid() ? item->valueToken : item->memberToken, + Comment::Front | Comment::Back_Inline); + + if (comment.isValid()) + m_listItemComments[item->memberToken.begin()] = comment; + } + + m_orphanComments[node] = findOrphanComments(node); + + return true; +} + +bool CommentAstVisitor::visit(UiPublicMember *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(FunctionDeclaration *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(UiImport *node) +{ + attachComment(node); + return true; +} diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h new file mode 100644 index 0000000000..dee2bb3983 --- /dev/null +++ b/tools/qmlformat/commentastvisitor.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 COMMENTASTVISITOR_H +#define COMMENTASTVISITOR_H + +#include +#include +#include + +#include +#include +#include + +using namespace QQmlJS::AST; + +struct Comment +{ + enum Location : int + { + Front = 1, + Back = Front << 1, + Back_Inline = Back << 1, + DefaultLocations = Front | Back_Inline, + AllLocations = Front | Back | Back_Inline + } m_location; + + Comment() {} + Comment(const QQmlJS::Engine *engine, Location location, QList srcLocations) + : m_location(location), m_srcLocations(srcLocations) { + for (const auto& srcLoc : srcLocations) { + m_text += engine->code().mid(static_cast(srcLoc.begin()), + static_cast(srcLoc.end() - srcLoc.begin())) + "\n"; + } + + m_text.chop(1); + } + + QList m_srcLocations; + + bool isValid() const { return !m_srcLocations.isEmpty(); } + bool isMultiline() const { return m_text.contains("\n"); } + bool isSyntheticMultiline() const { return m_srcLocations.size() > 1; } + + bool contains(const SourceLocation& location) const { + for (const SourceLocation& srcLoc : m_srcLocations) { + if (srcLoc.begin() == location.begin() && srcLoc.end() == location.end()) + return true; + } + + return false; + } + + QString m_text; +}; + +class CommentAstVisitor : protected Visitor +{ +public: + CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode); + + void throwRecursionDepthError() override {} + + const QHash attachedComments() const { return m_attachedComments; } + const QHash listComments() const { return m_listItemComments; } + const QHash> orphanComments() const { return m_orphanComments; } + + bool visit(UiScriptBinding *node) override; + bool visit(UiObjectBinding *node) override; + + bool visit(UiArrayBinding *node) override; + void endVisit(UiArrayBinding *node) override; + + bool visit(UiObjectDefinition *node) override; + void endVisit(UiObjectDefinition *) override; + + bool visit(UiEnumDeclaration *node) override; + void endVisit(UiEnumDeclaration *node) override; + + bool visit(UiEnumMemberList *node) override; + + bool visit(StatementList *node) override; + void endVisit(StatementList *node) override; + + bool visit(UiImport *node) override; + bool visit(UiPublicMember *node) override; + bool visit(FunctionDeclaration *node) override; +private: + bool isCommentAttached(const SourceLocation& location) const; + + QList findCommentsInLine(quint32 line, bool includePrevious=false) const; + + Comment findComment(SourceLocation first, SourceLocation last, + int locations = Comment::DefaultLocations) const; + + Comment findComment(Node *node, int locations = Comment::DefaultLocations) const; + QVector findOrphanComments(Node *node) const; + void attachComment(Node *node, int locations = Comment::DefaultLocations); + + QQmlJS::Engine *m_engine; + QHash m_attachedComments; + QHash m_listItemComments; + QHash> m_orphanComments; +}; + +#endif // COMMENTASTVISITOR_H diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp new file mode 100644 index 0000000000..09bb45c2bb --- /dev/null +++ b/tools/qmlformat/dumpastvisitor.cpp @@ -0,0 +1,927 @@ +/**************************************************************************** +** +** 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 "dumpastvisitor.h" + +DumpAstVisitor::DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment): m_comment(comment) +{ + // Add all completely orphaned comments + m_result += getOrphanedComments(nullptr); + + rootNode->accept(this); +} + +static QString parseUiQualifiedId(UiQualifiedId *id) +{ + QString name = id->name.toString(); + for (auto *item = id->next; item != nullptr; item = item->next) { + name += "." + item->name; + } + + return name; +} + +static QString operatorToString(int op) +{ + switch (op) + { + case QSOperator::Add: return "+"; + case QSOperator::And: return "&&"; + case QSOperator::InplaceAnd: return "&="; + case QSOperator::Assign: return "="; + case QSOperator::BitAnd: return "&"; + case QSOperator::BitOr: return "|"; + case QSOperator::BitXor: return "^"; + case QSOperator::InplaceSub: return "-="; + case QSOperator::Div: return "/"; + case QSOperator::InplaceDiv: return "/="; + case QSOperator::Equal: return "=="; + case QSOperator::Exp: return "**"; + case QSOperator::InplaceExp: return "**="; + case QSOperator::Ge: return ">="; + case QSOperator::Gt: return ">"; + case QSOperator::In: return "in"; + case QSOperator::InplaceAdd: return "+="; + case QSOperator::InstanceOf: return "instanceof"; + case QSOperator::Le: return "<="; + case QSOperator::LShift: return "<<"; + case QSOperator::InplaceLeftShift: return "<<="; + case QSOperator::Lt: return "<"; + case QSOperator::Mod: return "%"; + case QSOperator::InplaceMod: return "%="; + case QSOperator::Mul: return "*"; + case QSOperator::InplaceMul: return "*="; + case QSOperator::NotEqual: return "!="; + case QSOperator::Or: return "||"; + case QSOperator::InplaceOr: return "|="; + case QSOperator::RShift: return ">>"; + case QSOperator::InplaceRightShift: return ">>="; + case QSOperator::StrictEqual: return "==="; + case QSOperator::StrictNotEqual: return "!=="; + case QSOperator::Sub: return "-"; + case QSOperator::URShift: return ">>>"; + case QSOperator::InplaceURightShift: return ">>>="; + case QSOperator::InplaceXor: return "^="; + case QSOperator::As: return "as"; + case QSOperator::Coalesce: return "??"; + case QSOperator::Invalid: + default: + return "INVALID"; + } +} + +QString DumpAstVisitor::formatComment(const Comment &comment) const +{ + QString result; + + bool useMultilineComment = comment.isMultiline() && !comment.isSyntheticMultiline(); + + if (useMultilineComment) + result += "/*"; + else + result += "//"; + + result += comment.m_text; + + if (comment.isSyntheticMultiline()) + result = result.replace("\n","\n" + formatLine("//", false)); + + if (comment.m_location == Comment::Location::Back_Inline) + result.prepend(" "); + + if (useMultilineComment) + result += "*/"; + + return result; +} + +QString DumpAstVisitor::getComment(Node *node, Comment::Location location) const +{ + const auto& comments = m_comment->attachedComments(); + if (!comments.contains(node)) + return ""; + + auto comment = comments[node]; + + if (comment.m_location != location) + return ""; + + return formatComment(comment); +} + +QString DumpAstVisitor::getListItemComment(SourceLocation srcLocation, + Comment::Location location) const { + const auto& comments = m_comment->listComments(); + + if (!comments.contains(srcLocation.begin())) + return ""; + + auto comment = comments[srcLocation.begin()]; + + if (comment.m_location != location) + return ""; + + return formatComment(comment); +} + +QString DumpAstVisitor::getOrphanedComments(Node *node) const { + const auto& orphans = m_comment->orphanComments()[node]; + + if (orphans.size() == 0) + return ""; + + QString result = ""; + + for (const Comment& orphan : orphans) { + result += formatLine(formatComment(orphan)); + } + + result += "\n"; + + return result; +} + +QString DumpAstVisitor::parseArgumentList(ArgumentList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += parseExpression(item->expression) + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parseUiParameterList(UiParameterList *list) { + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += item->type->name + " " + item->name + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope) +{ + switch (element->type) + { + case PatternElement::Literal: + return parseExpression(element->initializer); + case PatternElement::Binding: { + QString result = ""; + QString expr = parseExpression(element->initializer); + + if (scope) { + switch (element->scope) { + case VariableScope::NoScope: + break; + case VariableScope::Let: + result = "let "; + break; + case VariableScope::Const: + result = "const "; + break; + case VariableScope::Var: + result = "var "; + break; + } + } + + result += element->bindingIdentifier.toString(); + + if (!expr.isEmpty()) + result += " = "+expr; + + return result; + } + default: + return "pe_unknown"; + } +} + +QString DumpAstVisitor::parsePatternElementList(PatternElementList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parseFormalParameterList(FormalParameterList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parseExpression(ExpressionNode *expression) +{ + if (expression == nullptr) + return ""; + + switch (expression->kind) + { + case Node::Kind_ArrayPattern: + return "["+parsePatternElementList(cast(expression)->elements)+"]"; + case Node::Kind_IdentifierExpression: + return cast(expression)->name.toString(); + case Node::Kind_FieldMemberExpression: { + auto *fieldMemberExpr = cast(expression); + return parseExpression(fieldMemberExpr->base) + "." + fieldMemberExpr->name.toString(); + } + case Node::Kind_ArrayMemberExpression: { + auto *arrayMemberExpr = cast(expression); + return parseExpression(arrayMemberExpr->base) + + "[" + parseExpression(arrayMemberExpr->expression) + "]"; + } + case Node::Kind_NestedExpression: + return "("+parseExpression(cast(expression)->expression)+")"; + case Node::Kind_TrueLiteral: + return "true"; + case Node::Kind_FalseLiteral: + return "false"; + case Node::Kind_FunctionExpression: + { + auto *functExpr = cast(expression); + + m_indentLevel++; + QString result = "function"; + + if (!functExpr->name.isEmpty()) + result += " " + functExpr->name; + + result += "("+parseFormalParameterList(functExpr->formals)+") {\n" + + parseStatementList(functExpr->body); + + m_indentLevel--; + + result += formatLine("}", false); + + return result; + } + case Node::Kind_NullExpression: + return "null"; + case Node::Kind_PostIncrementExpression: + return parseExpression(cast(expression)->base)+"++"; + case Node::Kind_PreIncrementExpression: + return "++"+parseExpression(cast(expression)->expression); + case Node::Kind_PostDecrementExpression: + return parseExpression(cast(expression)->base)+"--"; + case Node::Kind_PreDecrementExpression: + return "--"+parseExpression(cast(expression)->expression); + case Node::Kind_NumericLiteral: + return QString::number(cast(expression)->value); + case Node::Kind_StringLiteral: + return "\""+cast(expression)->value+"\""; + case Node::Kind_BinaryExpression: { + auto *binExpr = expression->binaryExpressionCast(); + return parseExpression(binExpr->left) + " " + operatorToString(binExpr->op) + + " " + parseExpression(binExpr->right); + } + case Node::Kind_CallExpression: { + auto *callExpr = cast(expression); + + return parseExpression(callExpr->base) + "(" + parseArgumentList(callExpr->arguments) + ")"; + } + case Node::Kind_NewExpression: + return "new "+parseExpression(cast(expression)->expression); + case Node::Kind_NewMemberExpression: { + auto *newMemberExpression = cast(expression); + return "new "+parseExpression(newMemberExpression->base) + + "(" +parseArgumentList(newMemberExpression->arguments)+")"; + } + case Node::Kind_DeleteExpression: + return "delete " + parseExpression(cast(expression)->expression); + case Node::Kind_VoidExpression: + return "void " + parseExpression(cast(expression)->expression); + case Node::Kind_TypeOfExpression: + return "typeof " + parseExpression(cast(expression)->expression); + case Node::Kind_UnaryPlusExpression: + return "+" + parseExpression(cast(expression)->expression); + case Node::Kind_UnaryMinusExpression: + return "-" + parseExpression(cast(expression)->expression); + case Node::Kind_NotExpression: + return "!" + parseExpression(cast(expression)->expression); + case Node::Kind_TildeExpression: + return "~" + parseExpression(cast(expression)->expression); + case Node::Kind_ConditionalExpression: { + auto *condExpr = cast(expression); + + QString result = ""; + + result += parseExpression(condExpr->expression) + " ? "; + result += parseExpression(condExpr->ok) + ": "; + result += parseExpression(condExpr->ko); + + return result; + } + case Node::Kind_YieldExpression: { + auto *yieldExpr = cast(expression); + + QString result = "yield"; + + if (yieldExpr->isYieldStar) + result += "*"; + + if (yieldExpr->expression) + result += " " + parseExpression(yieldExpr->expression); + + return result; + } + default: + m_error = true; + return "unknown_expression_"+QString::number(expression->kind); + } +} + +QString DumpAstVisitor::parseVariableDeclarationList(VariableDeclarationList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) { + result += parsePatternElement(item->declaration, (item == list)) + + (item->next != nullptr ? ", " : ""); + } + + return result; +} + +QString DumpAstVisitor::parseCaseBlock(CaseBlock *block) +{ + QString result = "{\n"; + + for (auto *item = block->clauses; item != nullptr; item = item->next) { + result += formatLine("case "+parseExpression(item->clause->expression)+":"); + m_indentLevel++; + result += parseStatementList(item->clause->statements); + m_indentLevel--; + } + + if (block->defaultClause) { + result += formatLine("default:"); + m_indentLevel++; + result += parseStatementList(block->defaultClause->statements); + m_indentLevel--; + } + + result += formatLine("}", false); + + return result; +} + +QString DumpAstVisitor::parseExportSpecifier(ExportSpecifier *specifier) +{ + QString result = specifier->identifier.toString(); + + if (!specifier->exportedIdentifier.isEmpty()) + result += " as " + specifier->exportedIdentifier; + + return result; +} + +QString DumpAstVisitor::parseExportsList(ExportsList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) { + result += formatLine(parseExportSpecifier(item->exportSpecifier) + + (item->next != nullptr ? "," : "")); + } + + return result; +} + +QString DumpAstVisitor::parseBlock(Block *block, bool hasNext, bool allowBraceless) +{ + bool hasOneLine = block->statements->next == nullptr && allowBraceless; + + QString result = hasOneLine ? "\n" : "{\n"; + m_indentLevel++; + result += parseStatementList(block->statements); + m_indentLevel--; + + if (hasNext) + result += formatLine(hasOneLine ? "" : "} ", false); + + if (!hasNext && !hasOneLine) + result += formatLine("}", false); + + m_blockNeededBraces |= (block->statements->next != nullptr); + + return result; +} + +QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, + bool blockAllowBraceless) +{ + if (statement == nullptr) + return ""; + + switch (statement->kind) + { + case Node::Kind_EmptyStatement: + return ""; + case Node::Kind_ExpressionStatement: + return parseExpression(cast(statement)->expression); + case Node::Kind_VariableStatement: + return parseVariableDeclarationList(cast(statement)->declarations); + case Node::Kind_ReturnStatement: + return "return "+parseExpression(cast(statement)->expression); + case Node::Kind_ContinueStatement: + return "continue"; + case Node::Kind_BreakStatement: + return "break"; + case Node::Kind_SwitchStatement: { + auto *switchStatement = cast(statement); + + QString result = "switch ("+parseExpression(switchStatement->expression)+") "; + + result += parseCaseBlock(switchStatement->block); + + return result; + } + case Node::Kind_IfStatement: { + auto *ifStatement = cast(statement); + + m_blockNeededBraces = false; + + QString if_false = parseStatement(ifStatement->ko, false, true); + QString if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), true); + + if (m_blockNeededBraces) { + if_false = parseStatement(ifStatement->ko, false, false); + if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), false); + } + + QString result = "if (" + parseExpression(ifStatement->expression) + ") "; + + result += if_true; + + if (!if_false.isEmpty()) + { + result += "else " + if_false; + } + + return result; + } + case Node::Kind_ForStatement: { + auto *forStatement = cast(statement); + + QString expr = parseExpression(forStatement->expression); + QString result = "for ("; + + result += parseVariableDeclarationList(forStatement->declarations); + + result += "; "; + + result += parseExpression(forStatement->condition) + "; "; + result += parseExpression(forStatement->expression)+") "; + + result += parseStatement(forStatement->statement); + + + return result; + } + case Node::Kind_ForEachStatement: { + auto *forEachStatement = cast(statement); + + QString result = "for ("; + + result += parsePatternElement(cast(forEachStatement->lhs)); + + switch (forEachStatement->type) + { + case ForEachType::In: + result += " in "; + break; + case ForEachType::Of: + result += " of "; + break; + } + + result += parseExpression(forEachStatement->expression) + ") "; + + result += parseStatement(forEachStatement->statement); + + return result; + } + case Node::Kind_WhileStatement: { + auto *whileStatement = cast(statement); + + return "while ("+parseExpression(whileStatement->expression) + ") " + + parseStatement(whileStatement->statement); + } + case Node::Kind_DoWhileStatement: { + auto *doWhileStatement = cast(statement); + return "do " + parseBlock(cast(doWhileStatement->statement), true, false) + + "while (" + parseExpression(doWhileStatement->expression) + ")"; + } + case Node::Kind_TryStatement: { + auto *tryStatement = cast(statement); + + Catch *catchExpr = tryStatement->catchExpression; + Finally *finallyExpr = tryStatement->finallyExpression; + + QString result; + + result += "try " + parseBlock(cast(tryStatement->statement), true, false); + + result += "catch (" + parsePatternElement(catchExpr->patternElement, false) + ") " + + parseBlock(cast(catchExpr->statement), finallyExpr, false); + + if (finallyExpr) { + result += "finally " + parseBlock(cast(tryStatement->statement), false, false); + } + + return result; + } + case Node::Kind_Block: { + return parseBlock(cast(statement), blockHasNext, blockAllowBraceless); + } + case Node::Kind_ThrowStatement: + return "throw "+parseExpression(cast(statement)->expression); + case Node::Kind_LabelledStatement: { + auto *labelledStatement = cast(statement); + QString result = labelledStatement->label+":\n"; + result += formatLine(parseStatement(labelledStatement->statement), false); + + return result; + } + case Node::Kind_WithStatement: { + auto *withStatement = cast(statement); + return "with (" + parseExpression(withStatement->expression) + ") " + + parseStatement(withStatement->statement); + } + case Node::Kind_DebuggerStatement: { + return "debugger"; + } + case Node::Kind_ExportDeclaration: + m_error = true; + return "export_decl_unsupported"; + case Node::Kind_ImportDeclaration: + m_error = true; + return "import_decl_unsupported"; + default: + m_error = true; + return "unknown_statement_"+QString::number(statement->kind); + } +} + +bool needsSemicolon(int kind) +{ + switch (kind) + { + case Node::Kind_ForStatement: + case Node::Kind_ForEachStatement: + case Node::Kind_IfStatement: + case Node::Kind_SwitchStatement: + case Node::Kind_WhileStatement: + case Node::Kind_DoWhileStatement: + case Node::Kind_TryStatement: + case Node::Kind_WithStatement: + return false; + default: + return true; + } +} + +QString DumpAstVisitor::parseStatementList(StatementList *list) +{ + QString result = ""; + + result += getOrphanedComments(list); + + for (auto *item = list; item != nullptr; item = item->next) { + QString statement = parseStatement(item->statement->statementCast()); + if (statement.isEmpty()) + continue; + + QString commentFront = getComment(item->statement, Comment::Location::Front); + QString commentBackInline = getComment(item->statement, Comment::Location::Back_Inline); + + if (!commentFront.isEmpty()) + result += formatLine(commentFront); + + result += formatLine(statement + (needsSemicolon(item->statement->kind) ? ";" : "") + + commentBackInline); + } + + return result; +} + +bool DumpAstVisitor::visit(UiPublicMember *node) { + addLine(getComment(node, Comment::Location::Front)); + + QString commentBackInline = getComment(node, Comment::Location::Back_Inline); + + switch (node->type) + { + case UiPublicMember::Signal: + if (m_firstSignal) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstSignal = false; + } + + addLine("signal "+node->name.toString()+"("+parseUiParameterList(node->parameters) + ")" + + commentBackInline); + break; + case UiPublicMember::Property: { + if (m_firstProperty) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstProperty = false; + } + + const bool is_required = node->requiredToken.isValid(); + const bool is_default = node->defaultToken.isValid(); + const bool is_readonly = node->readonlyToken.isValid(); + + QString prefix = ""; + QString statement = parseStatement(node->statement); + + if (!statement.isEmpty()) + statement.prepend(": "); + + if (is_required) + prefix += "required "; + + if (is_default) + prefix += "default "; + + if (is_readonly) + prefix += "readonly "; + + addLine(prefix + "property " + node->memberType->name + " " + + node->name+statement + commentBackInline); + break; + } + } + + return true; +} + +QString DumpAstVisitor::generateIndent() const { + constexpr int IDENT_WIDTH = 4; + + QString indent = ""; + for (int i = 0; i < IDENT_WIDTH*m_indentLevel; i++) + indent += " "; + + return indent; +} + +QString DumpAstVisitor::formatLine(QString line, bool newline) const { + QString result = generateIndent() + line; + if (newline) + result += "\n"; + + return result; +} + +void DumpAstVisitor::addNewLine(bool always) { + if (!always && m_result.endsWith("\n\n")) + return; + + m_result += "\n"; +} + +void DumpAstVisitor::addLine(QString line) { + // addLine does not support empty lines, use addNewLine(true) for that + if (line.isEmpty()) + return; + + m_result += formatLine(line); +} + +bool DumpAstVisitor::visit(UiObjectDefinition *node) { + if (m_firstObject) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstObject = false; + } + + addLine(getComment(node, Comment::Location::Front)); + addLine(node->qualifiedTypeNameId->name+" {"); + + m_indentLevel++; + + m_firstProperty = true; + m_firstSignal = true; + m_firstBinding = true; + m_firstObject = true; + m_firstOfAll = true; + + m_result += getOrphanedComments(node); + + return true; +} + +void DumpAstVisitor::endVisit(UiObjectDefinition *node) { + m_indentLevel--; + addLine(m_inArrayBinding && m_lastInArrayBinding != node ? "}," : "}"); + if (!m_inArrayBinding) + addNewLine(); +} + +bool DumpAstVisitor::visit(UiEnumDeclaration *node) { + + addNewLine(); + + addLine(getComment(node, Comment::Location::Front)); + addLine("enum " + node->name + " {"); + m_indentLevel++; + m_result += getOrphanedComments(node); + + return true; +} + +void DumpAstVisitor::endVisit(UiEnumDeclaration *) { + m_indentLevel--; + addLine("}"); + + addNewLine(); +} + +bool DumpAstVisitor::visit(UiEnumMemberList *node) { + for (auto *members = node; members != nullptr; members = members->next) { + + addLine(getListItemComment(members->memberToken, Comment::Location::Front)); + + QString line = members->member.toString(); + + if (members->valueToken.isValid()) + line += " = "+QString::number(members->value); + + if (members->next != nullptr) + line += ","; + + line += getListItemComment(members->memberToken, Comment::Location::Back_Inline); + + addLine(line); + } + + return true; +} + +bool DumpAstVisitor::visit(UiScriptBinding *node) { + if (m_firstBinding) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstBinding = false; + } + + addLine(getComment(node, Comment::Location::Front)); + addLine(parseUiQualifiedId(node->qualifiedId)+ ": " + parseStatement(node->statement) + + getComment(node, Comment::Location::Back_Inline)); + return true; +} + +bool DumpAstVisitor::visit(UiArrayBinding *node) { + if (m_firstBinding) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstBinding = false; + } + + addLine(getComment(node, Comment::Location::Front)); + addLine(parseUiQualifiedId(node->qualifiedId)+ ": ["); + + m_indentLevel++; + m_inArrayBinding = true; + + for (auto *item = node->members; item != nullptr; item = item->next) { + if (item->next == nullptr) + m_lastInArrayBinding = item->member; + } + + m_result += getOrphanedComments(node); + + return true; +} + +void DumpAstVisitor::endVisit(UiArrayBinding *) { + m_indentLevel--; + m_inArrayBinding = false; + m_lastInArrayBinding = nullptr; + addLine("]"); +} + +bool DumpAstVisitor::visit(FunctionDeclaration *node) { + + addNewLine(); + + addLine(getComment(node, Comment::Location::Front)); + addLine("function "+node->name+"("+parseFormalParameterList(node->formals)+") {"); + m_indentLevel++; + m_result += parseStatementList(node->body); + m_indentLevel--; + addLine("}"); + + addNewLine(); + + return true; +} + +bool DumpAstVisitor::visit(UiObjectBinding *node) { + if (m_firstObject) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstObject = false; + } + + QString name = parseUiQualifiedId(node->qualifiedTypeNameId); + + QString result = name; + + if (node->hasOnToken) + result += " on "+parseUiQualifiedId(node->qualifiedId); + + addNewLine(); + addLine(getComment(node, Comment::Location::Front)); + addLine(result+" {"); + + m_indentLevel++; + + return true; +} + +void DumpAstVisitor::endVisit(UiObjectBinding *) { + m_indentLevel--; + addLine("}"); + + addNewLine(); +} + +bool DumpAstVisitor::visit(UiImport *node) { + addLine(getComment(node, Comment::Location::Front)); + + QString result = "import "; + + if (!node->fileName.isEmpty()) + result += node->fileName; + else + result += parseUiQualifiedId(node->importUri); + + if (node->version) { + result += " " + QString::number(node->version->majorVersion) + "." + + QString::number(node->version->minorVersion); + } + + if (node->asToken.isValid()) { + result +=" as " + node->importId; + } + + result += getComment(node, Comment::Location::Back_Inline); + + addLine(result); + + return true; +} diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h new file mode 100644 index 0000000000..1348ee62e7 --- /dev/null +++ b/tools/qmlformat/dumpastvisitor.h @@ -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$ +** +****************************************************************************/ + +#ifndef DUMPAST_H +#define DUMPAST_H + +#include +#include + +#include "commentastvisitor.h" + +using namespace QQmlJS::AST; + +class DumpAstVisitor : protected Visitor +{ +public: + DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment); + + QString toString() const { return m_result; } + + bool visit(UiScriptBinding *node) override; + + bool visit(UiArrayBinding *node) override; + void endVisit(UiArrayBinding *node) override; + + bool visit(UiObjectBinding *node) override; + void endVisit(UiObjectBinding *node) override; + + bool visit(FunctionDeclaration *node) override; + + bool visit(UiObjectDefinition *node) override; + void endVisit(UiObjectDefinition *node) override; + + bool visit(UiEnumDeclaration *node) override; + void endVisit(UiEnumDeclaration *node) override; + + bool visit(UiEnumMemberList *node) override; + bool visit(UiPublicMember *node) override; + bool visit(UiImport *node) override; + + void throwRecursionDepthError() override {} + + bool error() const { return m_error; } +private: + QString generateIndent() const; + QString formatLine(QString line, bool newline=true) const; + + QString formatComment(const Comment &comment) const; + + QString getComment(Node *node, Comment::Location location) const; + QString getListItemComment(SourceLocation srcLocation, Comment::Location location) const; + + void addNewLine(bool always=false); + void addLine(QString line); + + QString getOrphanedComments(Node *node) const; + + QString parseStatement(Statement *statement, bool blockHasNext=false, + bool blockAllowBraceless=true); + QString parseStatementList(StatementList *list); + + QString parseExpression(ExpressionNode *expression); + + QString parsePatternElement(PatternElement *element, bool scope=true); + QString parsePatternElementList(PatternElementList *element); + + QString parseArgumentList(ArgumentList *list); + + QString parseUiParameterList(UiParameterList *list); + + QString parseVariableDeclarationList(VariableDeclarationList *list); + + QString parseCaseBlock(CaseBlock *block); + QString parseBlock(Block *block, bool hasNext, bool allowBraceless); + + QString parseExportsList(ExportsList *list); + QString parseExportSpecifier(ExportSpecifier *specifier); + + QString parseFormalParameterList(FormalParameterList *list); + + int m_indentLevel = 0; + + bool m_error = false; + bool m_blockNeededBraces = false; + bool m_inArrayBinding = false; + + bool m_firstOfAll = false; + bool m_firstSignal = false; + bool m_firstProperty = false; + bool m_firstBinding = false; + bool m_firstObject = true; + + UiObjectMember* m_lastInArrayBinding = nullptr; + QString m_result = ""; + CommentAstVisitor *m_comment; +}; + +#endif // DUMPAST_H diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp new file mode 100644 index 0000000000..bca788d316 --- /dev/null +++ b/tools/qmlformat/main.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** 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 +#include + +#include +#include +#include +#include +#include + +#if QT_CONFIG(commandlineparser) +#include +#endif + +#include "commentastvisitor.h" +#include "dumpastvisitor.h" +#include "restructureastvisitor.h" + +bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImports) +{ + QFile file(filename); + + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { + qWarning().noquote() << "Failed to open" << filename << "for reading."; + return false; + } + + QString code = QString::fromUtf8(file.readAll()); + file.close(); + + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + lexer.setCode(code, 1, true); + QQmlJS::Parser parser(&engine); + + bool success = parser.parse(); + + if (!success) { + const auto diagnosticMessages = parser.diagnosticMessages(); + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") + .arg(filename).arg(m.line).arg(m.message); + } + + qWarning().noquote() << "Failed to parse" << filename; + return false; + } + + // Try to attach comments to AST nodes + CommentAstVisitor comment(&engine, parser.rootNode()); + + if (verbose) + qWarning().noquote() << comment.attachedComments().size() << "comment(s) attached."; + + if (verbose) { + int orphaned = 0; + + for (const auto& orphanList : comment.orphanComments().values()) + orphaned += orphanList.size(); + + qWarning().noquote() << orphaned << "comments are orphans."; + } + + if (verbose && sortImports) + qWarning().noquote() << "Sorting imports"; + + // Do the actual restructuring + RestructureAstVisitor restructure(parser.rootNode(), sortImports); + + // Turn AST back into source code + if (verbose) + qWarning().noquote() << "Dumping" << filename; + + DumpAstVisitor dump(parser.rootNode(), &comment); + + if (dump.error()) + qWarning().noquote() << "An error has occurred. The output may not be reliable."; + + if (inplace) { + if (verbose) + qWarning().noquote() << "Writing to file" << filename; + + if (!file.open(QIODevice::Text | QIODevice::WriteOnly)) + { + qWarning().noquote() << "Failed to open" << filename << "for writing"; + return false; + } + + file.write(dump.toString().toUtf8()); + file.close(); + } else { + QTextStream(stdout) << dump.toString(); + } + + return true; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName("qmlformat"); + QCoreApplication::setApplicationVersion("1.0"); + + bool success = true; +#if QT_CONFIG(commandlineparser) + QCommandLineParser parser; + parser.setApplicationDescription("Formats QML files according to the QML Coding Conventions."); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addOption(QCommandLineOption({"V", "verbose"}, + QStringLiteral("Verbose mode. Outputs more detailed information."))); + + parser.addOption(QCommandLineOption({"n", "no-sort"}, + QStringLiteral("Do not sort imports."))); + + parser.addOption(QCommandLineOption({"i", "inplace"}, + QStringLiteral("Edit file in-place instead of outputting to stdout."))); + + parser.addPositionalArgument("filenames", "files to be processed by qmlformat"); + + parser.process(app); + + const auto positionalArguments = parser.positionalArguments(); + + if (positionalArguments.isEmpty()) + parser.showHelp(-1); + + for (const QString& file: parser.positionalArguments()) { + if (!parseFile(file, parser.isSet("inplace"), parser.isSet("verbose"), !parser.isSet("no-sort"))) + success = false; + } +#endif + + return success ? 0 : 1; +} diff --git a/tools/qmlformat/qmlformat.pro b/tools/qmlformat/qmlformat.pro new file mode 100644 index 0000000000..c8e74a4b55 --- /dev/null +++ b/tools/qmlformat/qmlformat.pro @@ -0,0 +1,17 @@ +option(host_build) + +QT = core qmldevtools-private + +SOURCES += main.cpp \ + commentastvisitor.cpp \ + dumpastvisitor.cpp \ + restructureastvisitor.cpp + +QMAKE_TARGET_DESCRIPTION = QML Formatter + +HEADERS += \ + commentastvisitor.h \ + dumpastvisitor.h \ + restructureastvisitor.h + +load(qt_tool) diff --git a/tools/qmlformat/restructureastvisitor.cpp b/tools/qmlformat/restructureastvisitor.cpp new file mode 100644 index 0000000000..f9ac2a20c2 --- /dev/null +++ b/tools/qmlformat/restructureastvisitor.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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 "restructureastvisitor.h" + +#include + +RestructureAstVisitor::RestructureAstVisitor(Node *rootNode, bool sortImports) : m_sortImports(sortImports) +{ + rootNode->accept(this); +} + +template +static QList findKind(UiObjectMemberList *list) +{ + QList members; + for (auto *item = list; item != nullptr; item = item->next) { + if (cast(item->member) != nullptr) + members.append(cast(item->member)); + } + + return members; +} + +template +static QList findKind(UiHeaderItemList *list) +{ + QList members; + for (auto *item = list; item != nullptr; item = item->next) { + if (cast(item->headerItem) != nullptr) + members.append(cast(item->headerItem)); + } + + return members; +} + +static QString parseUiQualifiedId(UiQualifiedId *id) +{ + QString name = id->name.toString(); + for (auto *item = id->next; item != nullptr; item = item->next) { + name += "." + item->name; + } + + return name; +} + +void RestructureAstVisitor::endVisit(UiHeaderItemList *node) +{ + QList correctOrder; + + auto imports = findKind(node); + + if (!m_sortImports) + return; + + // Sort imports + std::sort(imports.begin(), imports.end(), [](UiImport *a, UiImport *b) + { + auto nameA = a->fileName.isEmpty() ? parseUiQualifiedId(a->importUri) + : a->fileName.toString(); + auto nameB = b->fileName.isEmpty() ? parseUiQualifiedId(b->importUri) + : b->fileName.toString(); + + return nameA < nameB; + }); + + // Add imports + for (auto *import : imports) + correctOrder.append(import); + + // Add all the other items + for (auto *item = node; item != nullptr; item = item->next) { + if (!correctOrder.contains(item->headerItem)) + correctOrder.append(item->headerItem); + } + + // Rebuild member list from correctOrder + for (auto *item = node; item != nullptr; item = item->next) { + item->headerItem = correctOrder.front(); + correctOrder.pop_front(); + } +} + +void RestructureAstVisitor::endVisit(UiObjectMemberList *node) +{ + QList correctOrder; + + auto enumDeclarations = findKind(node); + auto scriptBindings = findKind(node); + auto arrayBindings = findKind(node); + auto publicMembers = findKind(node); + auto sourceElements = findKind(node); + auto objectDefinitions = findKind(node); + + // This structure is based on https://doc.qt.io/qt-5/qml-codingconventions.html + + // 1st id + for (auto *binding : scriptBindings) { + if (binding->qualifiedId->name == "id") { + correctOrder.append(binding); + + scriptBindings.removeOne(binding); + break; + } + } + + // 2nd enums + for (auto *enumDeclaration : enumDeclarations) + correctOrder.append(enumDeclaration); + + // 3rd property declarations + for (auto *publicMember : publicMembers) { + if (publicMember->type != UiPublicMember::Property) + continue; + + correctOrder.append(publicMember); + } + + // 4th signals + for (auto *publicMember : publicMembers) { + if (publicMember->type != UiPublicMember::Signal) + continue; + + correctOrder.append(publicMember); + } + + // 5th functions + for (auto *source : sourceElements) + correctOrder.append(source); + + // 6th properties + for (auto *binding : scriptBindings) + correctOrder.append(binding); + + for (auto *binding : arrayBindings) + correctOrder.append(binding); + + // 7th child objects + for (auto *objectDefinition : objectDefinitions) + correctOrder.append(objectDefinition); + + // 8th all the rest + for (auto *item = node; item != nullptr; item = item->next) { + if (!correctOrder.contains(item->member)) + correctOrder.append(item->member); + } + + // Rebuild member list from correctOrder + for (auto *item = node; item != nullptr; item = item->next) { + item->member = correctOrder.front(); + correctOrder.pop_front(); + } +} diff --git a/tools/qmlformat/restructureastvisitor.h b/tools/qmlformat/restructureastvisitor.h new file mode 100644 index 0000000000..a2195c8c2e --- /dev/null +++ b/tools/qmlformat/restructureastvisitor.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 RESTRUCTUREASTVISITOR_H +#define RESTRUCTUREASTVISITOR_H + +#include +#include + +using namespace QQmlJS::AST; + +class RestructureAstVisitor : protected Visitor +{ +public: + RestructureAstVisitor(Node *rootNode, bool sortImports); + + void throwRecursionDepthError() override {} + + void endVisit(UiObjectMemberList *node) override; + void endVisit(UiHeaderItemList *node) override; +private: + bool m_sortImports = false; +}; + +#endif // RESTRUCTUREASTVISITOR_H diff --git a/tools/tools.pro b/tools/tools.pro index 25ed760903..2e04f9330c 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -5,7 +5,8 @@ qtConfig(qml-devtools) { SUBDIRS += \ qmllint \ qmlmin \ - qmlimportscanner + qmlimportscanner \ + qmlformat qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen } -- cgit v1.2.3 From 813d70051c74b83e2e65a1a3358b759b50924a83 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Fri, 20 Dec 2019 12:48:25 +0100 Subject: tools/qmlformat: Improve formatting quite a bit - Fix strings not getting properly escaped - Fix if/else blocks not getting formatted correctly in some cases - Remove unnecessary newlines - Improve comment attachment Change-Id: Id8264c069315b3c6bd9ba8e8ac991dbdce8581a7 Reviewed-by: Ulf Hermann --- tools/qmlformat/commentastvisitor.cpp | 4 +- tools/qmlformat/dumpastvisitor.cpp | 84 ++++++++++++++++++++++++++++------- tools/qmlformat/dumpastvisitor.h | 2 +- 3 files changed, 72 insertions(+), 18 deletions(-) (limited to 'tools') diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp index 6d2bf49ddc..8264a6099e 100644 --- a/tools/qmlformat/commentastvisitor.cpp +++ b/tools/qmlformat/commentastvisitor.cpp @@ -198,13 +198,13 @@ void CommentAstVisitor::endVisit(StatementList *node) bool CommentAstVisitor::visit(UiObjectBinding *node) { - attachComment(node); + attachComment(node, Comment::Front | Comment::Back); return true; } bool CommentAstVisitor::visit(UiObjectDefinition *node) { - attachComment(node); + attachComment(node, Comment::Front | Comment::Back); return true; } diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp index 09bb45c2bb..1ab09e463b 100644 --- a/tools/qmlformat/dumpastvisitor.cpp +++ b/tools/qmlformat/dumpastvisitor.cpp @@ -34,6 +34,9 @@ DumpAstVisitor::DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment): m_co m_result += getOrphanedComments(nullptr); rootNode->accept(this); + + // We need to get rid of one new-line so our output doesn't append an empty line + m_result.chop(1); } static QString parseUiQualifiedId(UiQualifiedId *id) @@ -223,6 +226,17 @@ QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope) } } +QString escapeString(QString string) +{ + // Escape \r, \n and \t + string = string.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t"); + + // Escape " + string = string.replace("\"", "\\\""); + + return "\"" + string + "\""; +} + QString DumpAstVisitor::parsePatternElementList(PatternElementList *list) { QString result = ""; @@ -301,7 +315,7 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression) case Node::Kind_NumericLiteral: return QString::number(cast(expression)->value); case Node::Kind_StringLiteral: - return "\""+cast(expression)->value+"\""; + return escapeString(cast(expression)->value.toString()); case Node::Kind_BinaryExpression: { auto *binExpr = expression->binaryExpressionCast(); return parseExpression(binExpr->left) + " " + operatorToString(binExpr->op) @@ -339,7 +353,7 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression) QString result = ""; result += parseExpression(condExpr->expression) + " ? "; - result += parseExpression(condExpr->ok) + ": "; + result += parseExpression(condExpr->ok) + " : "; result += parseExpression(condExpr->ko); return result; @@ -474,21 +488,45 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, m_blockNeededBraces = false; - QString if_false = parseStatement(ifStatement->ko, false, true); - QString if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), true); + QString ifFalse = parseStatement(ifStatement->ko, false, true); + QString ifTrue = parseStatement(ifStatement->ok, !ifFalse.isEmpty(), true); + + bool ifTrueBlock = ifStatement->ok->kind == Node::Kind_Block; + bool ifFalseBlock = ifStatement->ko + ? (ifStatement->ko->kind == Node::Kind_Block || ifStatement->ko->kind == Node::Kind_IfStatement) + : false; if (m_blockNeededBraces) { - if_false = parseStatement(ifStatement->ko, false, false); - if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), false); + ifFalse = parseStatement(ifStatement->ko, false, false); + ifTrue = parseStatement(ifStatement->ok, !ifFalse.isEmpty(), false); } - QString result = "if (" + parseExpression(ifStatement->expression) + ") "; + QString result = "if (" + parseExpression(ifStatement->expression) + ")"; - result += if_true; + if (ifTrueBlock) { + result += " " + ifTrue; + } else { + result += "\n"; + m_indentLevel++; + result += formatLine(ifTrue); + m_indentLevel--; + } - if (!if_false.isEmpty()) + if (!ifFalse.isEmpty()) { - result += "else " + if_false; + if (ifTrueBlock) + result += "else"; + else + result += formatLine("else", false); + + if (ifFalseBlock) { + result += " " + ifFalse; + } else { + result += "\n"; + m_indentLevel++; + result += formatLine(ifFalse, false); + m_indentLevel--; + } } return result; @@ -537,8 +575,13 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, case Node::Kind_WhileStatement: { auto *whileStatement = cast(statement); - return "while ("+parseExpression(whileStatement->expression) + ") " - + parseStatement(whileStatement->statement); + m_blockNeededBraces = false; + + auto statement = parseStatement(whileStatement->statement, false, true); + + return "while ("+parseExpression(whileStatement->expression) + ")" + + (m_blockNeededBraces ? " " : "") + + statement; } case Node::Kind_DoWhileStatement: { auto *doWhileStatement = cast(statement); @@ -758,6 +801,7 @@ bool DumpAstVisitor::visit(UiObjectDefinition *node) { void DumpAstVisitor::endVisit(UiObjectDefinition *node) { m_indentLevel--; addLine(m_inArrayBinding && m_lastInArrayBinding != node ? "}," : "}"); + addLine(getComment(node, Comment::Location::Back)); if (!m_inArrayBinding) addNewLine(); } @@ -809,7 +853,8 @@ bool DumpAstVisitor::visit(UiScriptBinding *node) { else addNewLine(); - m_firstBinding = false; + if (parseUiQualifiedId(node->qualifiedId) != "id") + m_firstBinding = false; } addLine(getComment(node, Comment::Location::Front)); @@ -834,6 +879,12 @@ bool DumpAstVisitor::visit(UiArrayBinding *node) { m_indentLevel++; m_inArrayBinding = true; + m_firstOfAll = true; + m_firstObject = true; + m_firstSignal = true; + m_firstBinding = true; + m_firstProperty = true; + for (auto *item = node->members; item != nullptr; item = item->next) { if (item->next == nullptr) m_lastInArrayBinding = item->member; @@ -883,6 +934,8 @@ bool DumpAstVisitor::visit(UiObjectBinding *node) { if (node->hasOnToken) result += " on "+parseUiQualifiedId(node->qualifiedId); + else + result.prepend(parseUiQualifiedId(node->qualifiedId) + ": "); addNewLine(); addLine(getComment(node, Comment::Location::Front)); @@ -893,9 +946,10 @@ bool DumpAstVisitor::visit(UiObjectBinding *node) { return true; } -void DumpAstVisitor::endVisit(UiObjectBinding *) { +void DumpAstVisitor::endVisit(UiObjectBinding *node) { m_indentLevel--; addLine("}"); + addLine(getComment(node, Comment::Location::Back)); addNewLine(); } @@ -906,7 +960,7 @@ bool DumpAstVisitor::visit(UiImport *node) { QString result = "import "; if (!node->fileName.isEmpty()) - result += node->fileName; + result += escapeString(node->fileName.toString()); else result += parseUiQualifiedId(node->importUri); diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h index 1348ee62e7..67e45d4bb3 100644 --- a/tools/qmlformat/dumpastvisitor.h +++ b/tools/qmlformat/dumpastvisitor.h @@ -81,7 +81,7 @@ private: QString getOrphanedComments(Node *node) const; QString parseStatement(Statement *statement, bool blockHasNext=false, - bool blockAllowBraceless=true); + bool blockAllowBraceless = false); QString parseStatementList(StatementList *list); QString parseExpression(ExpressionNode *expression); -- cgit v1.2.3 From 88e1dd9e77f12ec94cc87872c5cb75d798bfdcbf Mon Sep 17 00:00:00 2001 From: Tasuku Suzuki Date: Sun, 22 Dec 2019 22:57:14 +0900 Subject: Fix -no-gui build on macOS Change-Id: Ifc520d58fc7294bad5c90e13ac72bb603f6fd33c Reviewed-by: Shawn Rutledge --- tools/qml/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp index f7d7b98277..7235c65aff 100644 --- a/tools/qml/main.cpp +++ b/tools/qml/main.cpp @@ -597,7 +597,7 @@ int main(int argc, char *argv[]) } if (files.count() <= 0) { -#if defined(Q_OS_DARWIN) +#if defined(Q_OS_DARWIN) && defined(QT_GUI_LIB) if (applicationType == QmlApplicationTypeGui) exitTimerId = static_cast(app.get())->startTimer(FILE_OPEN_EVENT_WAIT_TIME); else -- cgit v1.2.3 From 12d404ce54e6441b9564ab4b9c5b0b5d8d5c72e7 Mon Sep 17 00:00:00 2001 From: Tasuku Suzuki Date: Mon, 23 Dec 2019 02:39:17 +0900 Subject: Fix build without features.commandlineparser Change-Id: I07532fb5fe6ad61c5d5a7a145fde72b1336fb1f4 Reviewed-by: Ulf Hermann --- tools/tools.pro | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/tools.pro b/tools/tools.pro index 25ed760903..4cf40f4c92 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -11,8 +11,7 @@ qtConfig(qml-devtools) { } qtConfig(thread):!android|android_app:!wasm:!rtems { - SUBDIRS += \ - qml + qtConfig(commandlineparser): SUBDIRS += qml qtConfig(qml-profiler): SUBDIRS += qmlprofiler qtConfig(qml-preview): SUBDIRS += qmlpreview -- cgit v1.2.3 From e367e2c9dfcc2e832ce846829edf978d67291633 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 1 Jan 2020 00:37:58 +0100 Subject: Use the correct icon filename for QML Runtime on Windows Amends d7b7fed0cddf6236db3cf1bfdf9cc1380929cf5c. Task-number: QTBUG-70826 Change-Id: If9b74d19c4fc2bbe3ae882b4767919c74f3a38af Reviewed-by: Alessandro Portale --- tools/qml/qml.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/qml/qml.pro b/tools/qml/qml.pro index f086b7bff9..5dcbb3567a 100644 --- a/tools/qml/qml.pro +++ b/tools/qml/qml.pro @@ -8,7 +8,7 @@ RESOURCES += qml.qrc QMAKE_TARGET_DESCRIPTION = QML Runtime -ICON = resources/qml64.png +ICON = resources/qml-64.png win32 { RC_ICONS = resources/qml.ico } -- cgit v1.2.3 From 7d21113b054a6387687fb486def61dd2f2c4d99f Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Wed, 8 Jan 2020 09:11:34 +0100 Subject: qmlformat: Fix whitespace for parameter defaults Change-Id: I7958bf1546cc5854fc9b9856e36928ea932af0ca Reviewed-by: Ulf Hermann --- tools/qmlformat/commentastvisitor.h | 2 +- tools/qmlformat/dumpastvisitor.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h index dee2bb3983..1df907f8a3 100644 --- a/tools/qmlformat/commentastvisitor.h +++ b/tools/qmlformat/commentastvisitor.h @@ -113,7 +113,7 @@ public: private: bool isCommentAttached(const SourceLocation& location) const; - QList findCommentsInLine(quint32 line, bool includePrevious=false) const; + QList findCommentsInLine(quint32 line, bool includePrevious = false) const; Comment findComment(SourceLocation first, SourceLocation last, int locations = Comment::DefaultLocations) const; diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h index 67e45d4bb3..e73a7b628f 100644 --- a/tools/qmlformat/dumpastvisitor.h +++ b/tools/qmlformat/dumpastvisitor.h @@ -68,25 +68,25 @@ public: bool error() const { return m_error; } private: QString generateIndent() const; - QString formatLine(QString line, bool newline=true) const; + QString formatLine(QString line, bool newline = true) const; QString formatComment(const Comment &comment) const; QString getComment(Node *node, Comment::Location location) const; QString getListItemComment(SourceLocation srcLocation, Comment::Location location) const; - void addNewLine(bool always=false); + void addNewLine(bool always = false); void addLine(QString line); QString getOrphanedComments(Node *node) const; - QString parseStatement(Statement *statement, bool blockHasNext=false, + QString parseStatement(Statement *statement, bool blockHasNext = false, bool blockAllowBraceless = false); QString parseStatementList(StatementList *list); QString parseExpression(ExpressionNode *expression); - QString parsePatternElement(PatternElement *element, bool scope=true); + QString parsePatternElement(PatternElement *element, bool scope = true); QString parsePatternElementList(PatternElementList *element); QString parseArgumentList(ArgumentList *list); -- cgit v1.2.3 From 3e92cd7391855ff7189a3802db1e9fcc5e96ed13 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Wed, 8 Jan 2020 13:51:59 +0100 Subject: qmlformat/dumpastvisitor: Fix a few segfaults Change-Id: Ic8e95944cf36000a004d010293a532101433c3af Reviewed-by: Fabian Kosmale Reviewed-by: Ulf Hermann --- tools/qmlformat/dumpastvisitor.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp index 1ab09e463b..716be0a9a7 100644 --- a/tools/qmlformat/dumpastvisitor.cpp +++ b/tools/qmlformat/dumpastvisitor.cpp @@ -436,7 +436,7 @@ QString DumpAstVisitor::parseExportsList(ExportsList *list) QString DumpAstVisitor::parseBlock(Block *block, bool hasNext, bool allowBraceless) { - bool hasOneLine = block->statements->next == nullptr && allowBraceless; + bool hasOneLine = (block->statements == nullptr || block->statements->next == nullptr) && allowBraceless; QString result = hasOneLine ? "\n" : "{\n"; m_indentLevel++; @@ -449,7 +449,7 @@ QString DumpAstVisitor::parseBlock(Block *block, bool hasNext, bool allowBracele if (!hasNext && !hasOneLine) result += formatLine("}", false); - m_blockNeededBraces |= (block->statements->next != nullptr); + m_blockNeededBraces |= (block->statements && block->statements->next != nullptr); return result; } @@ -554,7 +554,12 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, QString result = "for ("; - result += parsePatternElement(cast(forEachStatement->lhs)); + PatternElement *patternElement = cast(forEachStatement->lhs); + + if (patternElement != nullptr) + result += parsePatternElement(patternElement); + else + result += parseExpression(forEachStatement->lhs->expressionCast()); switch (forEachStatement->type) { -- cgit v1.2.3 From 55a645a5ece45e933fb4553a76500d374e5c0a95 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 9 Jan 2020 17:27:38 +0100 Subject: qmllint: Clean up access to m_exportedName2Scope Don't accidentally overwrite the anonymous scope, and clearly mark where we want to modify the scopes and where we just want to retrieve them. Change-Id: I801d378ccaea6eb2548875766a9d3141dcb9f5e5 Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index af2bf1ae7e..f90b13ed29 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -203,11 +203,11 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn // add objects for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { const auto &val = it.value(); - m_exportedName2Scope[prefix + val->className()] = val; + m_exportedName2Scope.insert(prefix + val->className(), val); const auto exports = val->exports(); for (const auto &valExport : exports) - m_exportedName2Scope[prefix + valExport.type()] = val; + m_exportedName2Scope.insert(prefix + valExport.type(), val); const auto enums = val->enums(); for (const auto &valEnum : enums) @@ -401,17 +401,18 @@ void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const Q QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; while (it.hasNext()) { - const ScopeTree *scope = localQmlFile2ScopeTree(it.next()); - m_exportedName2Scope.insert(prefix + scope->className(), ScopeTree::ConstPtr(scope)); + ScopeTree::ConstPtr scope(localQmlFile2ScopeTree(it.next())); + if (!scope->className().isEmpty()) + m_exportedName2Scope.insert(prefix + scope->className(), scope); } } void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QString name) { for (;;) { - auto scope = m_exportedName2Scope[m_exportedName2Scope.contains(name) - ? name - : prefix + QLatin1Char('.') + name]; + ScopeTree::ConstPtr scope = m_exportedName2Scope.value(m_exportedName2Scope.contains(name) + ? name + : prefix + QLatin1Char('.') + name); if (scope) { const auto properties = scope->properties(); for (const auto &property : properties) @@ -460,7 +461,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) const auto exports = val->exports(); for (const auto &valExport : exports) - m_exportedName2Scope[valExport.type()] = val; + m_exportedName2Scope.insert(valExport.type(), val); const auto enums = val->enums(); for (const auto &valEnum : enums) @@ -468,7 +469,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) } // add "self" (as we only ever check the first part of a qualified identifier, we get away with // using an empty ScopeTree - m_exportedName2Scope[QFileInfo { m_filePath }.baseName()] = {}; + m_exportedName2Scope.insert(QFileInfo { m_filePath }.baseName(), {}); importDirectory(".", QString()); return true; @@ -602,7 +603,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) auto expstat = cast(uisb->statement); auto identexp = cast(expstat->expression); QString elementName = m_currentScope->name(); - m_qmlid2scope.insert(identexp->name.toString(), m_exportedName2Scope[elementName]); + m_qmlid2scope.insert(identexp->name.toString(), m_exportedName2Scope.value(elementName)); if (m_currentScope->isVisualRootScope()) m_rootId = identexp->name.toString(); } else { @@ -881,7 +882,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) do { scope = scope->parentScope(); // TODO: rename method } while (scope->scopeType() != ScopeType::QMLScope); - targetScope = m_exportedName2Scope[scope->name()]; + targetScope = m_exportedName2Scope.value(scope->name()); } else { // there was a target, check if we already can find it auto scopeIt = m_qmlid2scope.find(target); -- cgit v1.2.3 From 0989e5a2b7036674be8d43fac35a686fd04896c5 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 8 Nov 2019 18:43:31 +0100 Subject: qmllint: parse JS files for methods Change-Id: I3888231ac82f9babd51e6332af3c5457bf3c9141 Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 328 ++++++++++++++++++++++---------------- tools/qmllint/findunqualified.h | 8 +- 2 files changed, 196 insertions(+), 140 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index f90b13ed29..9cf26be4ad 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -40,6 +40,11 @@ #include #include +static const QString prefixedName(const QString &prefix, const QString &name) +{ + return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name); +} + static QQmlDirParser createQmldirParserForFile(const QString &filename) { QFile f(filename); @@ -67,6 +72,150 @@ void FindUnqualifiedIDVisitor::leaveEnvironment() m_currentScope = m_currentScope->parentScope(); } +void FindUnqualifiedIDVisitor::parseHeaders(QQmlJS::AST::UiHeaderItemList *header) +{ + using namespace QQmlJS::AST; + + while (header) { + if (auto import = cast(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; + } +} + +void FindUnqualifiedIDVisitor::parseMembers(QQmlJS::AST::UiObjectMemberList *member, + ScopeTree *scope) +{ + using namespace QQmlJS::AST; + + // member should be the sole element + Q_ASSERT(!member->next); + Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition); + auto definition = cast(member->member); + auto qualifiedId = definition->qualifiedTypeNameId; + while (qualifiedId && qualifiedId->next) { + qualifiedId = qualifiedId->next; + } + scope->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: { + // nothing to do + break; + } + case UiObjectMember::Kind_UiObjectBinding: { + // nothing to do + break; + } + case UiObjectMember::Kind_UiObjectDefinition: { + // creates nothing accessible + break; + } + case UiObjectMember::Kind_UiPublicMember: { + auto publicMember = cast(initMembers->member); + switch (publicMember->type) { + case UiPublicMember::Signal: { + UiParameterList *param = publicMember->parameters; + MetaMethod method; + method.setMethodType(MetaMethod::Signal); + method.setMethodName(publicMember->name.toString()); + while (param) { + method.addParameter(param->name.toString(), param->type->name.toString()); + param = param->next; + } + scope->addMethod(method); + break; + } + case UiPublicMember::Property: { + MetaProperty prop { + publicMember->name.toString(), + publicMember->typeModifier.toString(), + false, + false, + false, + 0 + }; + scope->addProperty(prop); + break; + } + } + break; + } + case UiObjectMember::Kind_UiScriptBinding: { + // does not create anything new, ignore + break; + } + case UiObjectMember::Kind_UiSourceElement: { + auto sourceElement = cast(initMembers->member); + if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { + MetaMethod method; + method.setMethodName(fexpr->name.toString()); + method.setMethodType(MetaMethod::Method); + FormalParameterList *parameters = fexpr->formals; + while (parameters) { + method.addParameter(parameters->element->bindingIdentifier.toString(), ""); + parameters = parameters->next; + } + scope->addMethod(method); + } else if (ClassExpression *clexpr = + sourceElement->sourceElement->asClassDefinition()) { + const MetaProperty prop { clexpr->name.toString(), "", false, false, false, 1 }; + scope->addProperty(prop); + } else if (cast(sourceElement->sourceElement)) { + // nothing to do + } else { + const auto loc = sourceElement->firstSourceLocation(); + m_colorOut.writeUncolored( + "unsupportedd sourceElement at " + + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn) + + QString::number(sourceElement->sourceElement->kind)); + } + break; + } + default: { + m_colorOut.writeUncolored("unsupported element of kind " + + QString::number(initMembers->member->kind)); + } + } + initMembers = initMembers->next; + } +} + +void FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program, ScopeTree *scope) +{ + using namespace QQmlJS::AST; + for (auto *statement = program->statements; statement; statement = statement->next) { + if (auto *function = cast(statement->statement)) { + MetaMethod method(function->name.toString()); + method.setMethodType(MetaMethod::Method); + for (auto *parameters = function->formals; parameters; parameters = parameters->next) + method.addParameter(parameters->element->bindingIdentifier.toString(), ""); + scope->addMethod(method); + } + } +} + enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; QStringList completeImportPaths(const QString &uri, const QString &basePath, int vmaj, int vmin) @@ -168,7 +317,7 @@ FindUnqualifiedIDVisitor::Import FindUnqualifiedIDVisitor::readQmldir(const QStr auto mo = qmlComponents.find(it.key()); if (mo == qmlComponents.end()) - mo = qmlComponents.insert(it.key(), localQmlFile2ScopeTree(filePath)); + mo = qmlComponents.insert(it.key(), localFile2ScopeTree(filePath)); (*mo)->addExport( it.key(), reader.typeNamespace(), @@ -203,11 +352,11 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn // add objects for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { const auto &val = it.value(); - m_exportedName2Scope.insert(prefix + val->className(), val); + m_exportedName2Scope.insert(prefixedName(prefix, val->className()), val); const auto exports = val->exports(); for (const auto &valExport : exports) - m_exportedName2Scope.insert(prefix + valExport.type(), val); + m_exportedName2Scope.insert(prefixedName(prefix, valExport.type()), val); const auto enums = val->enums(); for (const auto &valEnum : enums) @@ -248,11 +397,12 @@ void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString } } -ScopeTree *FindUnqualifiedIDVisitor::localQmlFile2ScopeTree(const QString &filePath) +ScopeTree *FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath) { using namespace QQmlJS::AST; auto scope = new ScopeTree(ScopeType::QMLScope); - QString baseName = QFileInfo { filePath }.baseName(); + const QFileInfo info { filePath }; + QString baseName = info.baseName(); scope->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); QFile file(filePath); if (!file.open(QFile::ReadOnly)) @@ -264,146 +414,48 @@ ScopeTree *FindUnqualifiedIDVisitor::localQmlFile2ScopeTree(const QString &fileP QQmlJS::Engine engine; QQmlJS::Lexer lexer(&engine); - lexer.setCode(code, 1, true); + const QString lowerSuffix = info.suffix().toLower(); + const bool isJavaScript = (lowerSuffix == QLatin1String("js") || lowerSuffix == QLatin1String("mjs")); + const bool isESModule = lowerSuffix == QLatin1String("mjs"); + lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript); QQmlJS::Parser parser(&engine); - if (!parser.parse()) { + + const bool success = isJavaScript ? (isESModule ? parser.parseModule() + : parser.parseProgram()) + : parser.parse(); + if (!success) return scope; + + if (!isJavaScript) { + QQmlJS::AST::UiProgram *program = parser.ast(); + parseHeaders(program->headers); + parseMembers(program->members, scope); + } else { + // TODO: Anything special to do with ES modules here? + parseProgram(QQmlJS::AST::cast(parser.rootNode()), scope); } - QQmlJS::AST::UiProgram *program = parser.ast(); - auto header = program->headers; - while (header) { - if (auto import = cast(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 = cast(member->member); - auto qualifiedId = definition->qualifiedTypeNameId; - while (qualifiedId && qualifiedId->next) { - qualifiedId = qualifiedId->next; - } - scope->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: { - // nothing to do - break; - } - case UiObjectMember::Kind_UiObjectBinding: { - // nothing to do - break; - } - case UiObjectMember::Kind_UiObjectDefinition: { - // creates nothing accessible - break; - } - case UiObjectMember::Kind_UiPublicMember: { - auto publicMember = cast(initMembers->member); - switch (publicMember->type) { - case UiPublicMember::Signal: { - UiParameterList *param = publicMember->parameters; - MetaMethod method; - method.setMethodType(MetaMethod::Signal); - method.setMethodName(publicMember->name.toString()); - while (param) { - method.addParameter(param->name.toString(), param->type->name.toString()); - param = param->next; - } - scope->addMethod(method); - break; - } - case UiPublicMember::Property: { - const MetaProperty property { - publicMember->name.toString(), - publicMember->typeModifier.toString(), - false, - false, - false, - 0 - }; - scope->addProperty(property); - break; - } - } - break; - } - case UiObjectMember::Kind_UiScriptBinding: { - // does not create anything new, ignore - break; - } - case UiObjectMember::Kind_UiSourceElement: { - auto sourceElement = cast(initMembers->member); - if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { - MetaMethod method; - method.setMethodName(fexpr->name.toString()); - method.setMethodType(MetaMethod::Method); - FormalParameterList *parameters = fexpr->formals; - while (parameters) { - method.addParameter(parameters->element->bindingIdentifier.toString(), ""); - parameters = parameters->next; - } - scope->addMethod(method); - } else if (ClassExpression *clexpr = - sourceElement->sourceElement->asClassDefinition()) { - const MetaProperty prop { clexpr->name.toString(), "", false, false, false, 1 }; - scope->addProperty(prop); - } else if (cast(sourceElement->sourceElement)) { - // nothing to do - } else { - const auto loc = sourceElement->firstSourceLocation(); - m_colorOut.writeUncolored( - "unsupportedd sourceElement at " - + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn) - + QString::number(sourceElement->sourceElement->kind)); - } - break; - } - default: { - m_colorOut.writeUncolored("unsupported element of kind " - + QString::number(initMembers->member->kind)); - } - } - initMembers = initMembers->next; - } + return scope; } -void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const QString &prefix) +void FindUnqualifiedIDVisitor::importFileOrDirectory(const QString &fileOrDirectory, + const QString &prefix) { - QString dirname = directory; - QFileInfo info { dirname }; - if (info.isRelative()) - dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname); + QString name = fileOrDirectory; + + if (QFileInfo(name).isRelative()) + name = QDir(QFileInfo { m_filePath }.path()).filePath(name); + + if (QFileInfo(name).isFile()) { + m_exportedName2Scope.insert(prefix, ScopeTree::ConstPtr(localFile2ScopeTree(name))); + return; + } - QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; + QDirIterator it { name, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; while (it.hasNext()) { - ScopeTree::ConstPtr scope(localQmlFile2ScopeTree(it.next())); + ScopeTree::ConstPtr scope(localFile2ScopeTree(it.next())); if (!scope->className().isEmpty()) - m_exportedName2Scope.insert(prefix + scope->className(), scope); + m_exportedName2Scope.insert(prefixedName(prefix, scope->className()), scope); } } @@ -471,7 +523,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) // using an empty ScopeTree m_exportedName2Scope.insert(QFileInfo { m_filePath }.baseName(), {}); - importDirectory(".", QString()); + importFileOrDirectory(".", QString()); return true; } @@ -778,11 +830,11 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) // construct path QString prefix = QLatin1String(""); if (import->asToken.isValid()) { - prefix += import->importId + QLatin1Char('.'); + prefix += import->importId; } auto dirname = import->fileName.toString(); if (!dirname.isEmpty()) - importDirectory(dirname, prefix); + importFileOrDirectory(dirname, prefix); QString path {}; if (!import->importId.isEmpty()) { diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 483a6da039..a70bf8032f 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -95,11 +95,15 @@ private: Import readQmldir(const QString &dirname); void processImport(const QString &prefix, const Import &import); - ScopeTree *localQmlFile2ScopeTree(const QString &filePath); + ScopeTree *localFile2ScopeTree(const QString &filePath); - void importDirectory(const QString &directory, const QString &prefix); + void importFileOrDirectory(const QString &directory, const QString &prefix); void importExportedNames(const QStringRef &prefix, QString name); + void parseHeaders(QQmlJS::AST::UiHeaderItemList *headers); + void parseMembers(QQmlJS::AST::UiObjectMemberList *members, ScopeTree *scope); + void parseProgram(QQmlJS::AST::Program *program, ScopeTree *scope); + void throwRecursionDepthError() override; // start block/scope handling -- cgit v1.2.3 From 728abd1dbce275b7778795f4588ef6588d0209da Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 8 Jan 2020 16:29:02 +0100 Subject: qmlformat: Fix default construction of Comment m_location would be uninitialized. Change-Id: Ieeeb0c0783a0b8e6f919019bd2ddcc1752993eaf Reviewed-by: Fabian Kosmale --- tools/qmlformat/commentastvisitor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h index 1df907f8a3..369784a5ba 100644 --- a/tools/qmlformat/commentastvisitor.h +++ b/tools/qmlformat/commentastvisitor.h @@ -48,9 +48,9 @@ struct Comment Back_Inline = Back << 1, DefaultLocations = Front | Back_Inline, AllLocations = Front | Back | Back_Inline - } m_location; + } m_location = Front; - Comment() {} + Comment() = default; Comment(const QQmlJS::Engine *engine, Location location, QList srcLocations) : m_location(location), m_srcLocations(srcLocations) { for (const auto& srcLoc : srcLocations) { -- cgit v1.2.3 From d0b2a3b5eb4021fb19b634e550cbc6f6664ad775 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 8 Nov 2019 14:48:32 +0100 Subject: qmllint: Analyze member access We can analyze access to many field member expressions and figure out if the accessed members exist. There are limits to this, of course. Generic JavaScript values are out of scope here. Change-Id: Id2e7613e56f06555cc3a2ba1c51683d9ea0bb84b Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 68 ++++++++---- tools/qmllint/findunqualified.h | 10 +- tools/qmllint/metatypes.h | 19 ++-- tools/qmllint/qmllint.pro | 2 +- tools/qmllint/scopetree.cpp | 225 ++++++++++++++++++++++++++++++++------ tools/qmllint/scopetree.h | 43 +++++--- 6 files changed, 290 insertions(+), 77 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 9cf26be4ad..ea28884f7b 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -62,9 +62,9 @@ static TypeDescriptionReader createQmltypesReaderForFile(const QString &filename return reader; } -void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name) +void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, const QString &name) { - m_currentScope = m_currentScope->createNewChildScope(type, std::move(name)); + m_currentScope = m_currentScope->createNewChildScope(type, name).get(); } void FindUnqualifiedIDVisitor::leaveEnvironment() @@ -150,7 +150,7 @@ void FindUnqualifiedIDVisitor::parseMembers(QQmlJS::AST::UiObjectMemberList *mem case UiPublicMember::Property: { MetaProperty prop { publicMember->name.toString(), - publicMember->typeModifier.toString(), + publicMember->memberType->name.toString(), false, false, false, @@ -352,6 +352,7 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn // add objects for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { const auto &val = it.value(); + m_types[it.key()] = val; m_exportedName2Scope.insert(prefixedName(prefix, val->className()), val); const auto exports = val->exports(); @@ -467,8 +468,10 @@ void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QSt : prefix + QLatin1Char('.') + name); if (scope) { const auto properties = scope->properties(); - for (const auto &property : properties) + for (auto property : properties) { + property.setType(m_exportedName2Scope.value(property.typeName()).get()); m_currentScope->insertPropertyIdentifier(property); + } m_currentScope->addMethods(scope->methods()); name = scope->superclassName(); @@ -508,8 +511,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) } } // add builtins - for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) { - auto val = ob_it.value(); + for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) { + auto val = objectIt.value(); + m_types[objectIt.key()] = val; const auto exports = val->exports(); for (const auto &valExport : exports) @@ -655,7 +659,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) auto expstat = cast(uisb->statement); auto identexp = cast(expstat->expression); QString elementName = m_currentScope->name(); - m_qmlid2scope.insert(identexp->name.toString(), m_exportedName2Scope.value(elementName)); + m_qmlid2scope.insert(identexp->name.toString(), m_currentScope); if (m_currentScope->isVisualRootScope()) m_rootId = identexp->name.toString(); } else { @@ -695,23 +699,23 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm) { // property bool inactive: !active // extract name inactive - m_currentScope->insertPropertyIdentifier(MetaProperty( + MetaProperty property( uipm->name.toString(), // TODO: signals, complex types etc. uipm->memberType ? uipm->memberType->name.toString() : QString(), uipm->typeModifier == QLatin1String("list"), !uipm->isReadonlyMember, - false, 0)); + false, 0); + property.setType(m_exportedName2Scope.value(property.typeName()).get()); + m_currentScope->insertPropertyIdentifier(property); return true; } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) { auto name = idexp->name; - if (!m_exportedName2Scope.contains(name.toString())) { - m_currentScope->addIdToAccssedIfNotInParentScopes( - { name.toString(), idexp->firstSourceLocation() }, m_unknownImports); - } + m_currentScope->addIdToAccessed(name.toString(), idexp->firstSourceLocation()); + m_fieldMemberBase = idexp; return true; } @@ -766,8 +770,8 @@ bool FindUnqualifiedIDVisitor::check() QScopedValueRollback rollback(m_currentScope, outstandingConnection.scope); outstandingConnection.uiod->initializer->accept(this); } - return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_rootScope.get(), m_rootId, - m_colorOut); + return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_exportedName2Scope, + m_rootScope.get(), m_rootId, m_colorOut); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) @@ -839,7 +843,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) QString path {}; if (!import->importId.isEmpty()) { // TODO: do not put imported ids into the same space as qml IDs - m_qmlid2scope.insert(import->importId.toString(), {}); + const QString importId = import->importId.toString(); + m_qmlid2scope.insert(importId, m_exportedName2Scope.value(importId).get()); } if (import->version) { auto uri = import->importUri; @@ -877,7 +882,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) } name.chop(1); - const MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, 0); + MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, 0); + prop.setType(m_exportedName2Scope.value(uiob->qualifiedTypeNameId->name.toString()).get()); m_currentScope->addProperty(prop); enterEnvironment(ScopeType::QMLScope, name); @@ -885,9 +891,15 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) return true; } -void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *) +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) { + const auto childScope = m_currentScope; leaveEnvironment(); + MetaProperty property(uiob->qualifiedId->name.toString(), + uiob->qualifiedTypeNameId->name.toString(), + false, true, true, 0); + property.setType(childScope); + m_currentScope->addProperty(property); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) @@ -927,14 +939,14 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) } member = member->next; } - ScopeTree::ConstPtr targetScope; + const ScopeTree *targetScope; 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); - targetScope = m_exportedName2Scope.value(scope->name()); + targetScope = m_exportedName2Scope.value(scope->name()).get(); } else { // there was a target, check if we already can find it auto scopeIt = m_qmlid2scope.find(target); @@ -967,3 +979,19 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) { leaveEnvironment(); } + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FieldMemberExpression *) +{ + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember) +{ + if (m_fieldMemberBase == fieldMember->base) { + m_currentScope->accessMember(fieldMember->name.toString(), + fieldMember->identifierToken); + m_fieldMemberBase = fieldMember; + } else { + m_fieldMemberBase = nullptr; + } +} diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index a70bf8032f..37ba259638 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -65,11 +65,13 @@ private: }; QScopedPointer m_rootScope; - ScopeTree *m_currentScope = nullptr; + ScopeTree *m_currentScope; + QQmlJS::AST::ExpressionNode *m_fieldMemberBase = nullptr; + QHash m_types; QHash m_exportedName2Scope; QStringList m_qmltypeDirs; QString m_code; - QHash m_qmlid2scope; + QHash m_qmlid2scope; QString m_rootId; QString m_filePath; QSet> m_alreadySeenImports; @@ -86,7 +88,7 @@ private: QVarLengthArray m_outstandingConnections; // Connections whose target we have not encountered - void enterEnvironment(ScopeType type, QString name); + void enterEnvironment(ScopeType type, const QString &name); void leaveEnvironment(); void importHelper(const QString &module, const QString &prefix = QString(), int major = -1, int minor = -1); @@ -158,6 +160,8 @@ private: bool visit(QQmlJS::AST::IdentifierExpression *idexp) override; bool visit(QQmlJS::AST::PatternElement *) override; + bool visit(QQmlJS::AST::FieldMemberExpression *idprop) override; + void endVisit(QQmlJS::AST::FieldMemberExpression *) override; }; #endif // FINDUNQUALIFIED_H diff --git a/tools/qmllint/metatypes.h b/tools/qmllint/metatypes.h index 24f8aa291e..4710ac1613 100644 --- a/tools/qmllint/metatypes.h +++ b/tools/qmllint/metatypes.h @@ -114,28 +114,33 @@ private: int m_revision = 0; }; +class ScopeTree; class MetaProperty { QString m_propertyName; - QString m_type; + QString m_typeName; + const ScopeTree *m_type = nullptr; bool m_isList; bool m_isWritable; bool m_isPointer; int m_revision; public: - MetaProperty(QString name, QString type, - bool isList, bool isWritable, bool isPointer, int revision) - : m_propertyName(std::move(name)) - , m_type(std::move(type)) + MetaProperty(QString propertyName, QString typeName, + bool isList, bool isWritable, bool isPointer, int revision) + : m_propertyName(std::move(propertyName)) + , m_typeName(std::move(typeName)) , m_isList(isList) , m_isWritable(isWritable) , m_isPointer(isPointer) , m_revision(revision) {} - QString name() const { return m_propertyName; } - QString typeName() const { return m_type; } + QString propertyName() const { return m_propertyName; } + QString typeName() const { return m_typeName; } + + void setType(const ScopeTree *type) { m_type = type; } + const ScopeTree *type() const { return m_type; } bool isList() const { return m_isList; } bool isWritable() const { return m_isWritable; } diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro index 2631768b81..8ab31bc83c 100644 --- a/tools/qmllint/qmllint.pro +++ b/tools/qmllint/qmllint.pro @@ -1,6 +1,6 @@ option(host_build) -QT = core qmldevtools-private +QT = core-private qmldevtools-private SOURCES += main.cpp \ componentversion.cpp \ diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index 7e9be92673..cac064eb27 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -36,13 +36,13 @@ ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) : m_parentScope(parentScope), m_name(std::move(name)), m_scopeType(type) {} -ScopeTree *ScopeTree::createNewChildScope(ScopeType type, QString name) +ScopeTree::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name) { Q_ASSERT(type != ScopeType::QMLScope || !m_parentScope || m_parentScope->m_scopeType == ScopeType::QMLScope || m_parentScope->m_name == "global"); - auto childScope = new ScopeTree{type, std::move(name), this}; + auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this}); m_childScopes.push_back(childScope); return childScope; } @@ -72,7 +72,7 @@ void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &meth void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) { addProperty(property); - MetaMethod method(property.name() + QLatin1String("Changed"), "void"); + MetaMethod method(property.propertyName() + QLatin1String("Changed"), "void"); addMethod(method); } @@ -87,21 +87,22 @@ bool ScopeTree::isIdInCurrentScope(const QString &id) const return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); } -void ScopeTree::addIdToAccssedIfNotInParentScopes( - const QPair &idLocationPair, - const QSet &unknownImports) +void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location) { + m_currentFieldMember = new FieldMemberList {id, location, {}}; + m_accessedIdentifiers.push_back(std::unique_ptr(m_currentFieldMember)); +} + +void ScopeTree::accessMember(const QString &name, const QQmlJS::AST::SourceLocation &location) { - // also do not add id if it is parent - // parent is almost always defined valid in QML, and if we could not find a definition for the current QML component - // not skipping "parent" will lead to many false positives - // Moreover, if the top level item is Item or inherits from it, it will have a parent property to which we would point the user - // which makes for a very nonsensical warning - const auto *qmlScope = currentQMLScope(); - if (!isIdInCurrentScope(idLocationPair.first) - && !(idLocationPair.first == QLatin1String("parent") - && qmlScope && unknownImports.contains(qmlScope->name()))) { - m_accessedIdentifiers.push_back(idLocationPair); - } + Q_ASSERT(m_currentFieldMember); + auto *fieldMember = new FieldMemberList {name, location, {}}; + m_currentFieldMember->m_child.reset(fieldMember); + m_currentFieldMember = fieldMember; +} + +void ScopeTree::resetMemberScope() +{ + m_currentFieldMember = nullptr; } bool ScopeTree::isVisualRootScope() const @@ -132,9 +133,121 @@ private: QStringRef m_afterText; }; +bool ScopeTree::checkMemberAccess( + const QString &code, + FieldMemberList *members, + const ScopeTree *scope, + const QHash &types, + ColorOutput& colorOut) const +{ + if (!members->m_child) + return true; + + Q_ASSERT(scope != nullptr); + + const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name(); + const auto &access = members->m_child; + + const auto scopeIt = scope->m_properties.find(access->m_name); + if (scopeIt != scope->m_properties.end()) { + if (scopeIt->isList() || scopeIt->typeName() == QLatin1String("string")) { + if (access->m_child && access->m_child->m_name != QLatin1String("length")) { + colorOut.write("Warning: ", Warning); + colorOut.write( + QString::fromLatin1( + "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n") + .arg(access->m_name) + .arg(QLatin1String(scopeIt->isList() ? "list" : "string")) + .arg(access->m_child->m_name) + .arg(access->m_child->m_location.startLine) + .arg(access->m_child->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_child->m_location); + return false; + } + return true; + } + const ScopeTree *type = scopeIt->type() ? scopeIt->type() + : types.value(scopeIt->typeName()).get(); + return checkMemberAccess(code, access.get(), type, types, colorOut); + } + + const auto scopeMethodIt = scope->m_methods.find(access->m_name); + if (scopeMethodIt != scope->m_methods.end()) + return true; // Access to property of JS function + + for (const auto enumerator : scope->m_enums) { + for (const QString &key : enumerator.keys()) { + if (access->m_name != key) + continue; + + if (!access->m_child) + return true; + + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "\"%1\" is an enum value. You cannot access \"%2\" on it at %3:%4\n") + .arg(access->m_name) + .arg(access->m_child->m_name) + .arg(access->m_child->m_location.startLine) + .arg(access->m_child->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_child->m_location); + return false; + } + } + + auto type = types.value(scopeName); + while (type) { + const auto typeIt = type->m_properties.find(access->m_name); + if (typeIt != type->m_properties.end()) { + const auto propType = typeIt->type(); + return checkMemberAccess(code, access.get(), + propType ? propType : types.value(typeIt->typeName()).get(), + types, colorOut); + } + + const auto typeMethodIt = type->m_methods.find(access->m_name); + if (typeMethodIt != type->m_methods.end()) { + if (access->m_child == nullptr) + return true; + + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "\"%1\" is a method. You cannot access \"%2\" on it at %3:%4\n") + .arg(access->m_name) + .arg(access->m_child->m_name) + .arg(access->m_child->m_location.startLine) + .arg(access->m_child->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_child->m_location); + return false; + } + + type = types.value(type->superclassName()); + } + + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "Property \"%1\" not found on type \"%2\" at %3:%4\n") + .arg(access->m_name) + .arg(scopeName) + .arg(access->m_location.startLine) + .arg(access->m_location.startColumn), Normal); + printContext(colorOut, code, access->m_location); + return false; +} + +static const QStringList unknownBuiltins = { + QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet + QStringLiteral("QRectF"), // TODO: should be added to builtins.qmltypes + QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values. + QStringLiteral("variant"), // Same for generic variants +}; + bool ScopeTree::recheckIdentifiers( - const QString &code, const QHash &qmlIDs, - const ScopeTree *root, const QString &rootId, ColorOutput &colorOut) const + const QString &code, + const QHash &qmlIDs, + const QHash &types, + const ScopeTree *root, const QString &rootId, + ColorOutput& colorOut) const { bool noUnqualifiedIdentifier = true; @@ -152,15 +265,61 @@ bool ScopeTree::recheckIdentifiers( printContext(colorOut, code, handler.second); } - for (const auto &idLocationPair : qAsConst(currentScope->m_accessedIdentifiers)) { - if (qmlIDs.contains(idLocationPair.first)) + for (const auto &memberAccessTree : qAsConst(currentScope->m_accessedIdentifiers)) { + if (currentScope->isIdInCurrentJSScopes(memberAccessTree->m_name)) + continue; + + auto it = qmlIDs.find(memberAccessTree->m_name); + if (it != qmlIDs.end()) { + if (!checkMemberAccess(code, memberAccessTree.get(), *it, types, colorOut)) + noUnqualifiedIdentifier = false; + continue; + } + + auto qmlScope = currentScope->currentQMLScope(); + if (qmlScope->methods().contains(memberAccessTree->m_name)) { + // a property of a JavaScript function + continue; + } + + const auto qmlIt = qmlScope->m_properties.find(memberAccessTree->m_name); + if (qmlIt != qmlScope->m_properties.end()) { + if (!memberAccessTree->m_child || unknownBuiltins.contains(qmlIt->typeName())) + continue; + + if (!qmlIt->type()) { + colorOut.write("Warning: ", Warning); + colorOut.write(QString::fromLatin1( + "Type of property \"%2\" not found at %3:%4\n") + .arg(memberAccessTree->m_name) + .arg(memberAccessTree->m_location.startLine) + .arg(memberAccessTree->m_location.startColumn), Normal); + printContext(colorOut, code, memberAccessTree->m_location); + noUnqualifiedIdentifier = false; + } else if (!checkMemberAccess(code, memberAccessTree.get(), qmlIt->type(), types, + colorOut)) { + noUnqualifiedIdentifier = false; + } + continue; - if (currentScope->isIdInCurrentScope(idLocationPair.first)) { + } + + // TODO: Lots of builtins are missing + if (memberAccessTree->m_name == "Qt") + continue; + + const auto typeIt = types.find(memberAccessTree->m_name); + if (typeIt != types.end()) { + if (!checkMemberAccess(code, memberAccessTree.get(), typeIt->get(), types, + colorOut)) { + noUnqualifiedIdentifier = false; + } continue; } + noUnqualifiedIdentifier = false; colorOut.write("Warning: ", Warning); - auto location = idLocationPair.second; + auto location = memberAccessTree->m_location; colorOut.write(QString::fromLatin1("unqualified access at %1:%2\n") .arg(location.startLine).arg(location.startColumn), Normal); @@ -169,11 +328,11 @@ bool ScopeTree::recheckIdentifiers( // root(JS) --> program(qml) --> (first element) const auto firstElement = root->m_childScopes[0]->m_childScopes[0]; - if (firstElement->m_properties.contains(idLocationPair.first) - || firstElement->m_methods.contains(idLocationPair.first) - || firstElement->m_enums.contains(idLocationPair.first)) { + if (firstElement->m_properties.contains(memberAccessTree->m_name) + || firstElement->m_methods.contains(memberAccessTree->m_name) + || firstElement->m_enums.contains(memberAccessTree->m_name)) { colorOut.write("Note: ", Info); - colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal ); + colorOut.write(memberAccessTree->m_name + QLatin1String(" is a meber of the root element\n"), Normal ); colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); if (rootId == QLatin1String("")) { colorOut.write("Note: ", Warning); @@ -184,10 +343,10 @@ bool ScopeTree::recheckIdentifiers( colorOut.write(rootId + QLatin1Char('.'), Hint); colorOut.write(issueLocationWithContext.issueText().toString(), Normal); colorOut.write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal); - } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) { + } else if (currentScope->isIdInjectedFromSignal(memberAccessTree->m_name)) { auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers - .values(idLocationPair.first); - auto location = idLocationPair.second; + .values(memberAccessTree->m_name); + auto location = memberAccessTree->m_location; // sort the list of signal handlers by their occurrence in the source code // then, we select the first one whose location is after the unqualified id // and go one step backwards to get the one which we actually need @@ -206,7 +365,7 @@ bool ScopeTree::recheckIdentifiers( auto methodUsage = *(--oneBehindIt); colorOut.write("Note:", Info); colorOut.write( - idLocationPair.first + QString::fromLatin1( + memberAccessTree->m_name + QString::fromLatin1( " is accessible in this scope because " "you are handling a signal at %1:%2\n") .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn), @@ -226,8 +385,8 @@ bool ScopeTree::recheckIdentifiers( } colorOut.write("\n\n\n", Normal); } - for (auto const& childScope: currentScope->m_childScopes) - workQueue.enqueue(childScope); + for (auto const &childScope: currentScope->m_childScopes) + workQueue.enqueue(childScope.get()); } return noUnqualifiedIdentifier; } diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index b7619c4352..00cb466eb9 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -104,11 +104,11 @@ public: int m_metaObjectRevision = 0; }; - ScopeTree(ScopeType type, QString name="", ScopeTree *parentScope=nullptr); - ~ScopeTree() { qDeleteAll(m_childScopes); } + ScopeTree(ScopeType type, QString name = QString(), + ScopeTree *parentScope = nullptr); - ScopeTree *createNewChildScope(ScopeType type, QString name); - ScopeTree *parentScope() { return m_parentScope; } + ScopeTree::Ptr createNewChildScope(ScopeType type, const QString &name); + ScopeTree *parentScope() const { return m_parentScope; } void insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope); void insertSignalIdentifier(const QString &id, const MetaMethod &method, @@ -119,16 +119,18 @@ public: const QQmlJS::AST::SourceLocation &location); bool isIdInCurrentScope(const QString &id) const; - void addIdToAccssedIfNotInParentScopes( - const QPair &idLocationPair, - const QSet &unknownImports); + void addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location); + void accessMember(const QString &name, const QQmlJS::AST::SourceLocation &location); + void resetMemberScope(); bool isVisualRootScope() const; QString name() const { return m_name; } bool recheckIdentifiers( - const QString &code, const QHash &qmlIDs, - const ScopeTree *root, const QString &rootId, ColorOutput &colorOut) const; + const QString &code, + const QHash &qmlIDs, + const QHash &types, + const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const; ScopeType scopeType() const { return m_scopeType; } @@ -149,7 +151,7 @@ public: void setSuperclassName(const QString &superclass) { m_superName = superclass; } QString superclassName() const { return m_superName; } - void addProperty(const MetaProperty &prop) { m_properties.insert(prop.name(), prop); } + void addProperty(const MetaProperty &prop) { m_properties.insert(prop.propertyName(), prop); } QHash properties() const { return m_properties; } QString defaultPropertyName() const { return m_defaultPropertyName; } @@ -166,6 +168,13 @@ public: void setIsComposite(bool value) { m_isSingleton = value; } private: + struct FieldMemberList + { + QString m_name; + QQmlJS::AST::SourceLocation m_location; + std::unique_ptr m_child; + }; + QSet m_jsIdentifiers; QMultiHash m_injectedSignalIdentifiers; @@ -173,11 +182,13 @@ private: QHash m_properties; QHash m_enums; - QVector> m_accessedIdentifiers; + std::vector> m_accessedIdentifiers; + FieldMemberList *m_currentFieldMember = nullptr; + QVector> m_unmatchedSignalHandlers; - QVector m_childScopes; - ScopeTree *m_parentScope = nullptr; + QVector m_childScopes; + ScopeTree *m_parentScope; QString m_name; QString m_className; @@ -198,6 +209,12 @@ private: const ScopeTree *currentQMLScope() const; void printContext(ColorOutput &colorOut, const QString &code, const QQmlJS::AST::SourceLocation &location) const; + bool checkMemberAccess( + const QString &code, + FieldMemberList *members, + const ScopeTree *scope, + const QHash &types, + ColorOutput& colorOut) const; }; #endif // SCOPETREE_H -- cgit v1.2.3 From 6c68a39a56299c72da30a4bac3ca7fe8420687a4 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 11 Nov 2019 17:35:09 +0100 Subject: qmllint: Resolve aliases Change-Id: Ida53af9774dc72559395064169113d0ee1f47f24 Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 159 +++++++------------------------ tools/qmllint/findunqualified.h | 3 +- tools/qmllint/importedmembersvisitor.cpp | 155 ++++++++++++++++++++++++++++++ tools/qmllint/importedmembersvisitor.h | 73 ++++++++++++++ tools/qmllint/metatypes.h | 6 +- tools/qmllint/qmllint.pro | 2 + tools/qmllint/typedescriptionreader.cpp | 2 +- 7 files changed, 272 insertions(+), 128 deletions(-) create mode 100644 tools/qmllint/importedmembersvisitor.cpp create mode 100644 tools/qmllint/importedmembersvisitor.h (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index ea28884f7b..359510f5f7 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "findunqualified.h" +#include "importedmembersvisitor.h" #include "scopetree.h" #include "typedescriptionreader.h" @@ -99,121 +100,21 @@ void FindUnqualifiedIDVisitor::parseHeaders(QQmlJS::AST::UiHeaderItemList *heade } } -void FindUnqualifiedIDVisitor::parseMembers(QQmlJS::AST::UiObjectMemberList *member, - ScopeTree *scope) -{ - using namespace QQmlJS::AST; - - // member should be the sole element - Q_ASSERT(!member->next); - Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition); - auto definition = cast(member->member); - auto qualifiedId = definition->qualifiedTypeNameId; - while (qualifiedId && qualifiedId->next) { - qualifiedId = qualifiedId->next; - } - scope->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: { - // nothing to do - break; - } - case UiObjectMember::Kind_UiObjectBinding: { - // nothing to do - break; - } - case UiObjectMember::Kind_UiObjectDefinition: { - // creates nothing accessible - break; - } - case UiObjectMember::Kind_UiPublicMember: { - auto publicMember = cast(initMembers->member); - switch (publicMember->type) { - case UiPublicMember::Signal: { - UiParameterList *param = publicMember->parameters; - MetaMethod method; - method.setMethodType(MetaMethod::Signal); - method.setMethodName(publicMember->name.toString()); - while (param) { - method.addParameter(param->name.toString(), param->type->name.toString()); - param = param->next; - } - scope->addMethod(method); - break; - } - case UiPublicMember::Property: { - MetaProperty prop { - publicMember->name.toString(), - publicMember->memberType->name.toString(), - false, - false, - false, - 0 - }; - scope->addProperty(prop); - break; - } - } - break; - } - case UiObjectMember::Kind_UiScriptBinding: { - // does not create anything new, ignore - break; - } - case UiObjectMember::Kind_UiSourceElement: { - auto sourceElement = cast(initMembers->member); - if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { - MetaMethod method; - method.setMethodName(fexpr->name.toString()); - method.setMethodType(MetaMethod::Method); - FormalParameterList *parameters = fexpr->formals; - while (parameters) { - method.addParameter(parameters->element->bindingIdentifier.toString(), ""); - parameters = parameters->next; - } - scope->addMethod(method); - } else if (ClassExpression *clexpr = - sourceElement->sourceElement->asClassDefinition()) { - const MetaProperty prop { clexpr->name.toString(), "", false, false, false, 1 }; - scope->addProperty(prop); - } else if (cast(sourceElement->sourceElement)) { - // nothing to do - } else { - const auto loc = sourceElement->firstSourceLocation(); - m_colorOut.writeUncolored( - "unsupportedd sourceElement at " - + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn) - + QString::number(sourceElement->sourceElement->kind)); - } - break; - } - default: { - m_colorOut.writeUncolored("unsupported element of kind " - + QString::number(initMembers->member->kind)); - } - } - initMembers = initMembers->next; - } -} - -void FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program, ScopeTree *scope) +ScopeTree *FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program, + const QString &name) { using namespace QQmlJS::AST; + ScopeTree *result = new ScopeTree(ScopeType::JSLexicalScope, name); for (auto *statement = program->statements; statement; statement = statement->next) { if (auto *function = cast(statement->statement)) { MetaMethod method(function->name.toString()); method.setMethodType(MetaMethod::Method); for (auto *parameters = function->formals; parameters; parameters = parameters->next) method.addParameter(parameters->element->bindingIdentifier.toString(), ""); - scope->addMethod(method); + result->addMethod(method); } } + return result; } enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; @@ -401,42 +302,47 @@ void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString ScopeTree *FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath) { using namespace QQmlJS::AST; - auto scope = new ScopeTree(ScopeType::QMLScope); const QFileInfo info { filePath }; QString baseName = info.baseName(); - scope->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); - QFile file(filePath); - if (!file.open(QFile::ReadOnly)) - return scope; - - QString code = file.readAll(); - file.close(); + const QString scopeName = baseName.endsWith(".ui") ? baseName.chopped(3) : baseName; QQmlJS::Engine engine; QQmlJS::Lexer lexer(&engine); const QString lowerSuffix = info.suffix().toLower(); - const bool isJavaScript = (lowerSuffix == QLatin1String("js") || lowerSuffix == QLatin1String("mjs")); const bool isESModule = lowerSuffix == QLatin1String("mjs"); + const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js"); + + QFile file(filePath); + if (!file.open(QFile::ReadOnly)) { + return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope, + scopeName); + } + + QString code = file.readAll(); + file.close(); + lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript); QQmlJS::Parser parser(&engine); const bool success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) : parser.parse(); - if (!success) - return scope; + if (!success) { + return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope, + scopeName); + } if (!isJavaScript) { QQmlJS::AST::UiProgram *program = parser.ast(); parseHeaders(program->headers); - parseMembers(program->members, scope); - } else { - // TODO: Anything special to do with ES modules here? - parseProgram(QQmlJS::AST::cast(parser.rootNode()), scope); + ImportedMembersVisitor membersVisitor(&m_colorOut); + program->members->accept(&membersVisitor); + return membersVisitor.result(scopeName); } - return scope; + // TODO: Anything special to do with ES modules here? + return parseProgram(QQmlJS::AST::cast(parser.rootNode()), scopeName); } void FindUnqualifiedIDVisitor::importFileOrDirectory(const QString &fileOrDirectory, @@ -705,7 +611,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm) uipm->memberType ? uipm->memberType->name.toString() : QString(), uipm->typeModifier == QLatin1String("list"), !uipm->isReadonlyMember, - false, 0); + false, + uipm->memberType ? (uipm->memberType->name == QLatin1String("alias")) : false, + 0); property.setType(m_exportedName2Scope.value(property.typeName()).get()); m_currentScope->insertPropertyIdentifier(property); return true; @@ -882,7 +790,8 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) } name.chop(1); - MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, 0); + MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, + name == QLatin1String("alias"), 0); prop.setType(m_exportedName2Scope.value(uiob->qualifiedTypeNameId->name.toString()).get()); m_currentScope->addProperty(prop); @@ -897,7 +806,9 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) leaveEnvironment(); MetaProperty property(uiob->qualifiedId->name.toString(), uiob->qualifiedTypeNameId->name.toString(), - false, true, true, 0); + false, true, true, + uiob->qualifiedTypeNameId->name == QLatin1String("alias"), + 0); property.setType(childScope); m_currentScope->addProperty(property); } diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 37ba259638..f5955a3a74 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -103,8 +103,7 @@ private: void importExportedNames(const QStringRef &prefix, QString name); void parseHeaders(QQmlJS::AST::UiHeaderItemList *headers); - void parseMembers(QQmlJS::AST::UiObjectMemberList *members, ScopeTree *scope); - void parseProgram(QQmlJS::AST::Program *program, ScopeTree *scope); + ScopeTree *parseProgram(QQmlJS::AST::Program *program, const QString &name); void throwRecursionDepthError() override; diff --git a/tools/qmllint/importedmembersvisitor.cpp b/tools/qmllint/importedmembersvisitor.cpp new file mode 100644 index 0000000000..676903d135 --- /dev/null +++ b/tools/qmllint/importedmembersvisitor.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "importedmembersvisitor.h" +#include "scopetree.h" + +using namespace QQmlJS::AST; + +ScopeTree *ImportedMembersVisitor::result(const QString &scopeName) const +{ + ScopeTree *result = new ScopeTree(ScopeType::QMLScope); + result->setClassName(scopeName); + result->setSuperclassName(m_rootObject->superclassName()); + const auto properties = m_rootObject->properties(); + for (auto property : properties) { + if (property.isAlias()) { + const auto it = m_objects.find(property.typeName()); + if (it != m_objects.end()) + property.setType(it->get()); + result->addProperty(property); + } else { + result->addProperty(property); + } + } + + for (const auto &method : m_rootObject->methods()) + result->addMethod(method); + + return result; +} + +bool ImportedMembersVisitor::visit(UiObjectDefinition *definition) +{ + ScopeTree::Ptr scope(new ScopeTree(ScopeType::QMLScope)); + auto qualifiedId = definition->qualifiedTypeNameId; + while (qualifiedId && qualifiedId->next) + qualifiedId = qualifiedId->next; + scope->setSuperclassName(qualifiedId->name.toString()); + if (!m_rootObject) + m_rootObject = scope; + m_currentObjects.append(scope); + return true; +} + +void ImportedMembersVisitor::endVisit(UiObjectDefinition *) +{ + m_currentObjects.pop_back(); +} + +bool ImportedMembersVisitor::visit(UiPublicMember *publicMember) +{ + switch (publicMember->type) { + case UiPublicMember::Signal: { + UiParameterList *param = publicMember->parameters; + MetaMethod method; + method.setMethodType(MetaMethod::Signal); + method.setMethodName(publicMember->name.toString()); + while (param) { + method.addParameter(param->name.toString(), param->type->name.toString()); + param = param->next; + } + currentObject()->addMethod(method); + break; + } + case UiPublicMember::Property: { + auto typeName = publicMember->memberType->name; + const bool isAlias = (typeName == QLatin1String("alias")); + if (isAlias) { + const auto expression = cast(publicMember->statement); + if (const auto idExpression = cast(expression->expression)) + typeName = idExpression->name; + } + MetaProperty prop { + publicMember->name.toString(), + typeName.toString(), + false, + false, + false, + isAlias, + 0 + }; + currentObject()->addProperty(prop); + break; + } + } + return true; +} + +bool ImportedMembersVisitor::visit(UiSourceElement *sourceElement) +{ + if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { + MetaMethod method; + method.setMethodName(fexpr->name.toString()); + method.setMethodType(MetaMethod::Method); + FormalParameterList *parameters = fexpr->formals; + while (parameters) { + method.addParameter(parameters->element->bindingIdentifier.toString(), ""); + parameters = parameters->next; + } + currentObject()->addMethod(method); + } else if (ClassExpression *clexpr = sourceElement->sourceElement->asClassDefinition()) { + MetaProperty prop { clexpr->name.toString(), "", false, false, false, false, 1 }; + currentObject()->addProperty(prop); + } else if (cast(sourceElement->sourceElement)) { + // nothing to do + } else { + const auto loc = sourceElement->firstSourceLocation(); + m_colorOut->writeUncolored( + "unsupportedd sourceElement at " + + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn) + + QString::number(sourceElement->sourceElement->kind)); + } + return true; +} + +bool ImportedMembersVisitor::visit(UiScriptBinding *scriptBinding) +{ + if (scriptBinding->qualifiedId->name == QLatin1String("id")) { + const auto *statement = cast(scriptBinding->statement); + const auto *idExprension = cast(statement->expression); + m_objects.insert(idExprension->name.toString(), currentObject()); + } + return true; +} + +void ImportedMembersVisitor::throwRecursionDepthError() +{ + m_colorOut->write(QStringLiteral("Error"), Error); + m_colorOut->write(QStringLiteral("Maximum statement or expression depth exceeded"), Error); +} diff --git a/tools/qmllint/importedmembersvisitor.h b/tools/qmllint/importedmembersvisitor.h new file mode 100644 index 0000000000..793ff18dcc --- /dev/null +++ b/tools/qmllint/importedmembersvisitor.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 IMPORTEDMEMBERSVISITOR_H +#define IMPORTEDMEMBERSVISITOR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include "scopetree.h" +#include "qcoloroutput.h" + +#include + +class ImportedMembersVisitor : public QQmlJS::AST::Visitor +{ +public: + ImportedMembersVisitor(ColorOutput *colorOut) : + m_colorOut(colorOut) + {} + + ScopeTree *result(const QString &scopeName) const; + +private: + bool visit(QQmlJS::AST::UiObjectDefinition *) override; + void endVisit(QQmlJS::AST::UiObjectDefinition *) override; + bool visit(QQmlJS::AST::UiPublicMember *) override; + bool visit(QQmlJS::AST::UiSourceElement *) override; + bool visit(QQmlJS::AST::UiScriptBinding *) override; + void throwRecursionDepthError() override; + + ScopeTree::Ptr currentObject() const { return m_currentObjects.back(); } + + QVector m_currentObjects; + ScopeTree::ConstPtr m_rootObject; + QHash m_objects; + + ColorOutput *m_colorOut = nullptr; +}; + +#endif // IMPORTEDMEMBERSVISITOR_H diff --git a/tools/qmllint/metatypes.h b/tools/qmllint/metatypes.h index 4710ac1613..d67de2edcd 100644 --- a/tools/qmllint/metatypes.h +++ b/tools/qmllint/metatypes.h @@ -123,16 +123,19 @@ class MetaProperty bool m_isList; bool m_isWritable; bool m_isPointer; + bool m_isAlias; int m_revision; public: MetaProperty(QString propertyName, QString typeName, - bool isList, bool isWritable, bool isPointer, int revision) + bool isList, bool isWritable, bool isPointer, bool isAlias, + int revision) : m_propertyName(std::move(propertyName)) , m_typeName(std::move(typeName)) , m_isList(isList) , m_isWritable(isWritable) , m_isPointer(isPointer) + , m_isAlias(isAlias) , m_revision(revision) {} @@ -145,6 +148,7 @@ public: bool isList() const { return m_isList; } bool isWritable() const { return m_isWritable; } bool isPointer() const { return m_isPointer; } + bool isAlias() const { return m_isAlias; } int revision() const { return m_revision; } }; diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro index 8ab31bc83c..4b7ca947cf 100644 --- a/tools/qmllint/qmllint.pro +++ b/tools/qmllint/qmllint.pro @@ -5,6 +5,7 @@ QT = core-private qmldevtools-private SOURCES += main.cpp \ componentversion.cpp \ findunqualified.cpp \ + importedmembersvisitor.cpp \ qcoloroutput.cpp \ scopetree.cpp \ typedescriptionreader.cpp @@ -16,6 +17,7 @@ load(qt_tool) HEADERS += \ componentversion.h \ findunqualified.h \ + importedmembersvisitor.h \ metatypes.h \ qcoloroutput.h \ scopetree.h \ diff --git a/tools/qmllint/typedescriptionreader.cpp b/tools/qmllint/typedescriptionreader.cpp index 4f0cc88800..3dc87ffc8d 100644 --- a/tools/qmllint/typedescriptionreader.cpp +++ b/tools/qmllint/typedescriptionreader.cpp @@ -380,7 +380,7 @@ void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTre return; } - scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, revision)); + scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, false, revision)); } void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope) -- cgit v1.2.3 From 2677de3c79de984e88e4a950c915b4f3f54786dd Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 14 Nov 2019 15:59:56 +0100 Subject: qmllint: Assume "parent" property is always document parent ... except if the document parent is Component. Then leave the type alone. Change-Id: Id7b2e6efdefe18a8d375967ddedcdf9e07a07946 Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 2 ++ tools/qmllint/scopetree.cpp | 9 +++++++++ tools/qmllint/scopetree.h | 1 + 3 files changed, 12 insertions(+) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 359510f5f7..d0dc3a3711 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -888,7 +888,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::PatternElement *element) void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) { + auto childScope = m_currentScope; leaveEnvironment(); + childScope->updateParentProperty(m_currentScope); } bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FieldMemberExpression *) diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index cac064eb27..e5b0eecc5f 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -448,6 +448,15 @@ void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevis m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision); } +void ScopeTree::updateParentProperty(const ScopeTree *scope) +{ + auto it = m_properties.find(QLatin1String("parent")); + if (it != m_properties.end() + && scope->name() != QLatin1String("Component") + && scope->name() != QLatin1String("program")) + it->setType(scope); +} + ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version, int metaObjectRevision) : m_package(std::move(package)), diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index 00cb466eb9..eb5f384477 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -153,6 +153,7 @@ public: void addProperty(const MetaProperty &prop) { m_properties.insert(prop.propertyName(), prop); } QHash properties() const { return m_properties; } + void updateParentProperty(const ScopeTree *scope); QString defaultPropertyName() const { return m_defaultPropertyName; } void setDefaultPropertyName(const QString &name) { m_defaultPropertyName = name; } -- cgit v1.2.3 From 01b865411d945960cb00b51a0078549eb3dd38e8 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 15 Nov 2019 16:59:46 +0100 Subject: qmllint: parse simple type assertions Change-Id: Ic24018137d8f989686d4a8f927efb824453d114b Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 28 +++++++++++++++++++++++++++- tools/qmllint/findunqualified.h | 3 +++ tools/qmllint/scopetree.cpp | 20 +++++++++++++------- tools/qmllint/scopetree.h | 4 +++- 4 files changed, 46 insertions(+), 9 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index d0dc3a3711..dc1bb29567 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -900,11 +900,37 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FieldMemberExpression *) void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember) { - if (m_fieldMemberBase == fieldMember->base) { + using namespace QQmlJS::AST; + ExpressionNode *base = fieldMember->base; + while (auto *nested = cast(base)) + base = nested->expression; + + if (m_fieldMemberBase == base) { + QString type; + if (auto *binary = cast(base)) { + if (binary->op == QSOperator::As) { + if (auto *right = cast(binary->right)) + type = right->name.toString(); + } + } m_currentScope->accessMember(fieldMember->name.toString(), + type, fieldMember->identifierToken); m_fieldMemberBase = fieldMember; } else { m_fieldMemberBase = nullptr; } } + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::BinaryExpression *) +{ + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp) +{ + if (binExp->op == QSOperator::As && m_fieldMemberBase == binExp->left) + m_fieldMemberBase = binExp; + else + m_fieldMemberBase = nullptr; +} diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index f5955a3a74..6668b53b08 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -161,6 +161,9 @@ private: bool visit(QQmlJS::AST::PatternElement *) override; bool visit(QQmlJS::AST::FieldMemberExpression *idprop) override; void endVisit(QQmlJS::AST::FieldMemberExpression *) override; + + bool visit(QQmlJS::AST::BinaryExpression *) override; + void endVisit(QQmlJS::AST::BinaryExpression *) override; }; #endif // FINDUNQUALIFIED_H diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index e5b0eecc5f..6c122f0368 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -88,14 +88,15 @@ bool ScopeTree::isIdInCurrentScope(const QString &id) const } void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location) { - m_currentFieldMember = new FieldMemberList {id, location, {}}; + m_currentFieldMember = new FieldMemberList {id, QString(), location, {}}; m_accessedIdentifiers.push_back(std::unique_ptr(m_currentFieldMember)); } -void ScopeTree::accessMember(const QString &name, const QQmlJS::AST::SourceLocation &location) +void ScopeTree::accessMember(const QString &name, const QString &parentType, + const QQmlJS::AST::SourceLocation &location) { Q_ASSERT(m_currentFieldMember); - auto *fieldMember = new FieldMemberList {name, location, {}}; + auto *fieldMember = new FieldMemberList {name, parentType, location, {}}; m_currentFieldMember->m_child.reset(fieldMember); m_currentFieldMember = fieldMember; } @@ -150,7 +151,9 @@ bool ScopeTree::checkMemberAccess( const auto scopeIt = scope->m_properties.find(access->m_name); if (scopeIt != scope->m_properties.end()) { - if (scopeIt->isList() || scopeIt->typeName() == QLatin1String("string")) { + const QString typeName = access->m_parentType.isEmpty() ? scopeIt->typeName() + : access->m_parentType; + if (scopeIt->isList() || typeName == QLatin1String("string")) { if (access->m_child && access->m_child->m_name != QLatin1String("length")) { colorOut.write("Warning: ", Warning); colorOut.write( @@ -166,8 +169,9 @@ bool ScopeTree::checkMemberAccess( } return true; } - const ScopeTree *type = scopeIt->type() ? scopeIt->type() - : types.value(scopeIt->typeName()).get(); + const ScopeTree *type = (scopeIt->type() && access->m_parentType.isEmpty()) + ? scopeIt->type() + : types.value(typeName).get(); return checkMemberAccess(code, access.get(), type, types, colorOut); } @@ -199,7 +203,9 @@ bool ScopeTree::checkMemberAccess( while (type) { const auto typeIt = type->m_properties.find(access->m_name); if (typeIt != type->m_properties.end()) { - const auto propType = typeIt->type(); + const ScopeTree *propType = access->m_parentType.isEmpty() + ? typeIt->type() + : types.value(access->m_parentType).get(); return checkMemberAccess(code, access.get(), propType ? propType : types.value(typeIt->typeName()).get(), types, colorOut); diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index eb5f384477..f5d1155a49 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -120,7 +120,8 @@ public: bool isIdInCurrentScope(const QString &id) const; void addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location); - void accessMember(const QString &name, const QQmlJS::AST::SourceLocation &location); + void accessMember(const QString &name, const QString &parentType, + const QQmlJS::AST::SourceLocation &location); void resetMemberScope(); bool isVisualRootScope() const; @@ -172,6 +173,7 @@ private: struct FieldMemberList { QString m_name; + QString m_parentType; QQmlJS::AST::SourceLocation m_location; std::unique_ptr m_child; }; -- cgit v1.2.3 From d31bb553b543cc43591c31a35d054504aa756bdc Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 15 Nov 2019 17:42:18 +0100 Subject: qmllint: Consider attached properties We cannot figure out who attached properties to what, therefore we consider any uppercase property we cannot locate as a potential attached property. Change-Id: I60508b9e4e038a51b87c56edf0796ac492aa3fa0 Reviewed-by: Fabian Kosmale --- tools/qmllint/scopetree.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'tools') diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index 6c122f0368..2ca3ed9a67 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -230,6 +230,16 @@ bool ScopeTree::checkMemberAccess( type = types.value(type->superclassName()); } + if (access->m_name.front().isUpper() && scope->scopeType() == ScopeType::QMLScope) { + // may be an attached type + const auto it = types.find(access->m_name); + if (it != types.end() && !(*it)->attachedTypeName().isEmpty()) { + const auto attached = types.find((*it)->attachedTypeName()); + if (attached != types.end()) + return checkMemberAccess(code, access.get(), attached->get(), types, colorOut); + } + } + colorOut.write("Warning: ", Warning); colorOut.write(QString::fromLatin1( "Property \"%1\" not found on type \"%2\" at %3:%4\n") -- cgit v1.2.3 From 3dc5b937c4e9acf83ee54e870390c22f341c29c8 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 14 Nov 2019 16:31:48 +0100 Subject: qmllint: Consider failure to find imports an error There is no real reason to tolerate it. We can generate all qmltypes at build time, and if we can't find them, the results are likely to be off. Change-Id: Id935d76ac3ca6b601381b40a73d7fbacca342c4d Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index dc1bb29567..88466cfae9 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -388,6 +388,7 @@ void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QSt m_colorOut.write(name + QLatin1String(" was not found." " Did you add all import paths?\n")); m_unknownImports.insert(name); + m_visitFailed = true; break; } } -- cgit v1.2.3 From 82030ac616821b6ec614112b74e6e2f46b808c17 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 10 Dec 2019 15:26:18 +0100 Subject: Adapt to the removal of QObject::staticQtMetaObject Qt::staticMetaObject should be used instead Change-Id: I1e02884f0b1f07e9c51d065f11bdc51c793b7173 Reviewed-by: Simon Hausmann Reviewed-by: Thiago Macieira --- tools/qmlplugindump/main.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'tools') diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp index 1556718471..947b5dff27 100644 --- a/tools/qmlplugindump/main.cpp +++ b/tools/qmlplugindump/main.cpp @@ -179,15 +179,6 @@ void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, } } -/* We want to add the MetaObject for 'Qt' to the list, this is a - simple way to access it. -*/ -class FriendlyQObject: public QObject -{ -public: - static const QMetaObject *qtMeta() { return &staticQtMetaObject; } -}; - /* When we dump a QMetaObject, we want to list all the types it is exported as. To do this, we need to find the QQmlTypes associated with this QMetaObject. @@ -258,7 +249,7 @@ QSet collectReachableMetaObjects(QQmlEngine *engine, ) { QSet metas; - metas.insert(FriendlyQObject::qtMeta()); + metas.insert(&Qt::staticMetaObject); const auto qmlTypes = QQmlMetaType::qmlTypes(); for (const QQmlType &ty : qmlTypes) { -- cgit v1.2.3 From f215d00f601f66c15e0d4b2951223bc6f7e3ea5e Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Tue, 17 Dec 2019 09:29:31 +0100 Subject: Make QtQml work with the latest changes in qtbase Adjust the code to work with the new QString and QVector data structures that have inlined size and data pointers. Fix a large bunch of compiler warnings from QFlags. Update dependencies for qtbase and qtsvg Change-Id: Iba237aed90c140b822e0cf501b9fb7156ec27c2d Reviewed-by: Fabian Kosmale --- tools/qmllint/findunqualified.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 88466cfae9..807110c3c1 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -540,12 +540,12 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *) leaveEnvironment(); } -static QString signalName(const QStringRef &handlerName) +static QString signalName(QStringView handlerName) { - if (handlerName.startsWith("on") && handlerName.size() > 2) { + if (handlerName.startsWith(u"on") && handlerName.size() > 2) { QString signal = handlerName.mid(2).toString(); for (int i = 0; i < signal.length(); ++i) { - QCharRef ch = signal[i]; + QChar &ch = signal[i]; if (ch.isLower()) return QString(); if (ch.isUpper()) { -- cgit v1.2.3 From 4527d87e8f24e99658020900d9eb114d86d4dc82 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 15 Jan 2020 14:11:49 +0100 Subject: Remove qmlmin It was deprecated in 5.15. Now we can remove it. Change-Id: Id50af37206af246207e24d077906eaf913dc796a Reviewed-by: Maximilian Goldstein Reviewed-by: Fabian Kosmale --- tools/qmlmin/main.cpp | 700 ------------------------------------------------ tools/qmlmin/qmlmin.pro | 7 - tools/tools.pro | 3 +- 3 files changed, 1 insertion(+), 709 deletions(-) delete mode 100644 tools/qmlmin/main.cpp delete mode 100644 tools/qmlmin/qmlmin.pro (limited to 'tools') diff --git a/tools/qmlmin/main.cpp b/tools/qmlmin/main.cpp deleted file mode 100644 index 3c9b3c7251..0000000000 --- a/tools/qmlmin/main.cpp +++ /dev/null @@ -1,700 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -// -// QML/JS minifier -// -namespace QQmlJS { - -enum RegExpFlag { - Global = 0x01, - IgnoreCase = 0x02, - Multiline = 0x04 -}; - - -class QmlminLexer: protected Lexer, public Directives -{ - QQmlJS::Engine _engine; - QString _fileName; - QString _directives; - -protected: - QVector _stateStack; - QList _tokens; - QList _tokenStrings; - int yytoken = -1; - QString yytokentext; - - void lex() { - if (_tokens.isEmpty()) { - _tokens.append(Lexer::lex()); - _tokenStrings.append(tokenText()); - } - - yytoken = _tokens.takeFirst(); - yytokentext = _tokenStrings.takeFirst(); - } - - int lookaheadToken() - { - if (yytoken < 0) - lex(); - return yytoken; - } - - void pushToken(int token) - { - _tokens.prepend(yytoken); - _tokenStrings.prepend(yytokentext); - yytoken = token; - yytokentext = QString(); - } - -public: - QmlminLexer() - : Lexer(&_engine), _stateStack(128) {} - virtual ~QmlminLexer() {} - - QString fileName() const { return _fileName; } - - bool operator()(const QString &fileName, const QString &code) - { - int startToken = T_FEED_JS_SCRIPT; - const QFileInfo fileInfo(fileName); - if (fileInfo.suffix().toLower() == QLatin1String("qml")) - startToken = T_FEED_UI_PROGRAM; - setCode(code, /*line = */ 1, /*qmlMode = */ startToken == T_FEED_UI_PROGRAM); - _fileName = fileName; - _directives.clear(); - return parse(startToken); - } - - QString directives() - { - return _directives; - } - - // - // Handle the .pragma/.import directives - // - void pragmaLibrary() override - { - _directives += QLatin1String(".pragma library\n"); - } - - void importFile(const QString &jsfile, const QString &module, int line, int column) override - { - _directives += QLatin1String(".import"); - _directives += QLatin1Char('"'); - _directives += quote(jsfile); - _directives += QLatin1Char('"'); - _directives += QLatin1String("as "); - _directives += module; - _directives += QLatin1Char('\n'); - Q_UNUSED(line); - Q_UNUSED(column); - } - - void importModule(const QString &uri, const QString &version, const QString &module, int line, int column) override - { - _directives += QLatin1String(".import "); - _directives += uri; - _directives += QLatin1Char(' '); - _directives += version; - _directives += QLatin1String(" as "); - _directives += module; - _directives += QLatin1Char('\n'); - Q_UNUSED(line); - Q_UNUSED(column); - } - -protected: - virtual bool parse(int startToken) = 0; - - static QString quote(const QString &string) - { - QString quotedString; - for (const QChar &ch : string) { - if (ch == QLatin1Char('"')) - quotedString += QLatin1String("\\\""); - else { - if (ch == QLatin1Char('\\')) quotedString += QLatin1String("\\\\"); - else if (ch == QLatin1Char('\"')) quotedString += QLatin1String("\\\""); - else if (ch == QLatin1Char('\b')) quotedString += QLatin1String("\\b"); - else if (ch == QLatin1Char('\f')) quotedString += QLatin1String("\\f"); - else if (ch == QLatin1Char('\n')) quotedString += QLatin1String("\\n"); - else if (ch == QLatin1Char('\r')) quotedString += QLatin1String("\\r"); - else if (ch == QLatin1Char('\t')) quotedString += QLatin1String("\\t"); - else if (ch == QLatin1Char('\v')) quotedString += QLatin1String("\\v"); - else if (ch == QLatin1Char('\0')) quotedString += QLatin1String("\\0"); - else quotedString += ch; - } - } - return quotedString; - } - - bool isIdentChar(const QChar &ch) const - { - if (ch.isLetterOrNumber()) - return true; - else if (ch == QLatin1Char('_') || ch == QLatin1Char('$')) - return true; - return false; - } - - bool isRegExpRule(int ruleno) const - { - return ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 || - ruleno == J_SCRIPT_REGEXPLITERAL_RULE2; - } - - void handleLookaheads(int ruleno) { - if (ruleno == J_SCRIPT_EXPRESSIONSTATEMENTLOOKAHEAD_RULE) { - int token = lookaheadToken(); - if (token == T_LBRACE) - pushToken(T_FORCE_BLOCK); - else if (token == T_FUNCTION || token == T_CLASS || token == T_LET || token == T_CONST) - pushToken(T_FORCE_DECLARATION); - } else if (ruleno == J_SCRIPT_CONCISEBODYLOOKAHEAD_RULE) { - int token = lookaheadToken(); - if (token == T_LBRACE) - pushToken(T_FORCE_BLOCK); - } else if (ruleno == J_SCRIPT_EXPORTDECLARATIONLOOKAHEAD_RULE) { - int token = lookaheadToken(); - if (token == T_FUNCTION || token == T_CLASS) - pushToken(T_FORCE_DECLARATION); - } - } - - bool scanRestOfRegExp(int ruleno, QString *restOfRegExp) - { - if (! scanRegExp(ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 ? Lexer::NoPrefix : Lexer::EqualPrefix)) - return false; - - *restOfRegExp = regExpPattern(); - if (ruleno == J_SCRIPT_REGEXPLITERAL_RULE2) { - Q_ASSERT(! restOfRegExp->isEmpty()); - Q_ASSERT(restOfRegExp->at(0) == QLatin1Char('=')); - *restOfRegExp = restOfRegExp->mid(1); // strip the prefix - } - *restOfRegExp += QLatin1Char('/'); - const RegExpFlag flags = (RegExpFlag) regExpFlags(); - if (flags & Global) - *restOfRegExp += QLatin1Char('g'); - if (flags & IgnoreCase) - *restOfRegExp += QLatin1Char('i'); - if (flags & Multiline) - *restOfRegExp += QLatin1Char('m'); - - if (regExpFlags() == 0) { - // Add an extra space after the regexp literal delimiter (aka '/'). - // This will avoid possible problems when pasting tokens like `instanceof' - // after the regexp literal. - *restOfRegExp += QLatin1Char(' '); - } - return true; - } -}; - - -class Minify: public QmlminLexer -{ - QString _minifiedCode; - int _maxWidth; - int _width; - -public: - Minify(int maxWidth); - - QString minifiedCode() const; - -protected: - void append(const QString &s); - bool parse(int startToken) override; - void escape(const QChar &ch, QString *out); -}; - -Minify::Minify(int maxWidth) - : _maxWidth(maxWidth), _width(0) -{ -} - -QString Minify::minifiedCode() const -{ - return _minifiedCode; -} - -void Minify::append(const QString &s) -{ - if (!s.isEmpty()) { - if (_maxWidth) { - // Prefer not to exceed the maximum chars per line (but don't break up segments) - int segmentLength = s.count(); - if (_width && ((_width + segmentLength) > _maxWidth)) { - _minifiedCode.append(QLatin1Char('\n')); - _width = 0; - } - - _width += segmentLength; - } - - _minifiedCode.append(s); - } -} - -void Minify::escape(const QChar &ch, QString *out) -{ - out->append(QLatin1String("\\u")); - const QString hx = QString::number(ch.unicode(), 16); - switch (hx.length()) { - case 1: out->append(QLatin1String("000")); break; - case 2: out->append(QLatin1String("00")); break; - case 3: out->append(QLatin1Char('0')); break; - case 4: break; - default: Q_ASSERT(!"unreachable"); - } - out->append(hx); -} - -bool Minify::parse(int startToken) -{ - int yyaction = 0; - int yytos = -1; - QString assembled; - - _minifiedCode.clear(); - _tokens.append(startToken); - _tokenStrings.append(QString()); - - if (startToken == T_FEED_JS_SCRIPT) { - // parse optional pragma directive - DiagnosticMessage error; - if (scanDirectives(this, &error)) { - // append the scanned directives to the minifier code. - append(directives()); - - _tokens.append(tokenKind()); - _tokenStrings.append(tokenText()); - } else { - std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' - << tokenStartColumn() << ": syntax error" << std::endl; - return false; - } - } - - do { - if (++yytos == _stateStack.size()) - _stateStack.resize(_stateStack.size() * 2); - - _stateStack[yytos] = yyaction; - - again: - if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) - lex(); - - yyaction = t_action(yyaction, yytoken); - if (yyaction > 0) { - if (yyaction == ACCEPT_STATE) { - --yytos; - if (!assembled.isEmpty()) - append(assembled); - return true; - } - - const QChar lastChar = assembled.isEmpty() ? (_minifiedCode.isEmpty() ? QChar() - : _minifiedCode.at(_minifiedCode.length() - 1)) - : assembled.at(assembled.length() - 1); - - if (yytoken == T_SEMICOLON) { - assembled += QLatin1Char(';'); - - append(assembled); - assembled.clear(); - - } else if (yytoken == T_PLUS || yytoken == T_MINUS || yytoken == T_PLUS_PLUS || yytoken == T_MINUS_MINUS) { - if (lastChar == QLatin1Char(spell[yytoken][0])) { - // don't merge unary signs, additive expressions and postfix/prefix increments. - assembled += QLatin1Char(' '); - } - - assembled += QLatin1String(spell[yytoken]); - - } else if (yytoken == T_NUMERIC_LITERAL) { - if (isIdentChar(lastChar)) - assembled += QLatin1Char(' '); - - if (yytokentext.startsWith('.')) - assembled += QLatin1Char('0'); - - assembled += yytokentext; - - if (assembled.endsWith(QLatin1Char('.'))) - assembled += QLatin1Char('0'); - - } else if (yytoken == T_IDENTIFIER) { - QString identifier = yytokentext; - - if (classify(identifier.constData(), identifier.size(), qmlMode()) != T_IDENTIFIER) { - // the unescaped identifier is a keyword. In this case just replace - // the last character of the identifier with it escape sequence. - const QChar ch = identifier.at(identifier.length() - 1); - identifier.chop(1); - escape(ch, &identifier); - } - - if (isIdentChar(lastChar)) - assembled += QLatin1Char(' '); - - assembled += identifier; - - } else if (yytoken == T_STRING_LITERAL || yytoken == T_MULTILINE_STRING_LITERAL) { - assembled += QLatin1Char('"'); - assembled += quote(yytokentext); - assembled += QLatin1Char('"'); - } else { - if (isIdentChar(lastChar)) { - if (! yytokentext.isEmpty()) { - const QChar ch = yytokentext.at(0); - if (isIdentChar(ch)) - assembled += QLatin1Char(' '); - } - } - assembled += yytokentext; - } - yytoken = -1; - } else if (yyaction < 0) { - const int ruleno = -yyaction - 1; - yytos -= rhs[ruleno]; - - handleLookaheads(ruleno); - - if (isRegExpRule(ruleno)) { - QString restOfRegExp; - - if (! scanRestOfRegExp(ruleno, &restOfRegExp)) - break; // break the loop, it wil report a syntax error - - assembled += restOfRegExp; - } - yyaction = nt_action(_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT); - } - } while (yyaction); - - const int yyerrorstate = _stateStack[yytos]; - - // automatic insertion of `;' - if (yytoken != -1 && ((t_action(yyerrorstate, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken)) - || t_action(yyerrorstate, T_COMPATIBILITY_SEMICOLON))) { - _tokens.prepend(yytoken); - _tokenStrings.prepend(yytokentext); - yyaction = yyerrorstate; - yytoken = T_SEMICOLON; - goto again; - } - - std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' << tokenStartColumn() - << ": syntax error" << std::endl; - return false; -} - - -class Tokenize: public QmlminLexer -{ - QStringList _minifiedCode; - -public: - Tokenize() {} - - QStringList tokenStream() const; - -protected: - bool parse(int startToken) override; -}; - -QStringList Tokenize::tokenStream() const -{ - return _minifiedCode; -} - -bool Tokenize::parse(int startToken) -{ - int yyaction = 0; - int yytos = -1; - - _minifiedCode.clear(); - _tokens.append(startToken); - _tokenStrings.append(QString()); - - if (startToken == T_FEED_JS_SCRIPT) { - // parse optional pragma directive - DiagnosticMessage error; - if (scanDirectives(this, &error)) { - // append the scanned directives as one token to - // the token stream. - _minifiedCode.append(directives()); - - _tokens.append(tokenKind()); - _tokenStrings.append(tokenText()); - } else { - std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' - << tokenStartColumn() << ": syntax error" << std::endl; - return false; - } - } - - do { - if (++yytos == _stateStack.size()) - _stateStack.resize(_stateStack.size() * 2); - - _stateStack[yytos] = yyaction; - - again: - if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) - lex(); - - yyaction = t_action(yyaction, yytoken); - if (yyaction > 0) { - if (yyaction == ACCEPT_STATE) { - --yytos; - return true; - } - - if (yytoken == T_SEMICOLON) - _minifiedCode += QLatin1String(";"); - else - _minifiedCode += yytokentext; - - yytoken = -1; - } else if (yyaction < 0) { - const int ruleno = -yyaction - 1; - yytos -= rhs[ruleno]; - - handleLookaheads(ruleno); - - if (isRegExpRule(ruleno)) { - QString restOfRegExp; - - if (! scanRestOfRegExp(ruleno, &restOfRegExp)) - break; // break the loop, it wil report a syntax error - - _minifiedCode.last().append(restOfRegExp); - } - - yyaction = nt_action(_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT); - } - } while (yyaction); - - const int yyerrorstate = _stateStack[yytos]; - - // automatic insertion of `;' - if (yytoken != -1 && ((t_action(yyerrorstate, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken)) - || t_action(yyerrorstate, T_COMPATIBILITY_SEMICOLON))) { - _tokens.prepend(yytoken); - _tokenStrings.prepend(yytokentext); - yyaction = yyerrorstate; - yytoken = T_SEMICOLON; - goto again; - } - - std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' - << tokenStartColumn() << ": syntax error" << std::endl; - return false; -} - -} // end of QQmlJS namespace - -static void usage(bool showHelp = false) -{ - std::cerr << "Usage: qmlmin [options] file" << std::endl; - - if (showHelp) { - std::cerr << " Removes comments and layout characters" << std::endl - << " The options are:" << std::endl - << " -o write output to file rather than stdout" << std::endl - << " -v --verify-only just run the verifier, no output" << std::endl - << " -w restrict line characters to width" << std::endl - << " -h display this output" << std::endl; - } -} - -int runQmlmin(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); - - const QStringList args = app.arguments(); - - QString fileName; - QString outputFile; - bool verifyOnly = false; - - // By default ensure the output character width is less than 16-bits (pass 0 to disable) - int width = USHRT_MAX; - - int index = 1; - while (index < args.size()) { - const QString arg = args.at(index++); - const QString next = index < args.size() ? args.at(index) : QString(); - - if (arg == QLatin1String("-h") || arg == QLatin1String("--help")) { - usage(/*showHelp*/ true); - return 0; - } else if (arg == QLatin1String("-v") || arg == QLatin1String("--verify-only")) { - verifyOnly = true; - } else if (arg == QLatin1String("-o")) { - if (next.isEmpty()) { - std::cerr << "qmlmin: argument to '-o' is missing" << std::endl; - return EXIT_FAILURE; - } else { - outputFile = next; - ++index; // consume the next argument - } - } else if (arg.startsWith(QLatin1String("-o"))) { - outputFile = arg.mid(2); - - if (outputFile.isEmpty()) { - std::cerr << "qmlmin: argument to '-o' is missing" << std::endl; - return EXIT_FAILURE; - } - } else if (arg == QLatin1String("-w")) { - if (next.isEmpty()) { - std::cerr << "qmlmin: argument to '-w' is missing" << std::endl; - return EXIT_FAILURE; - } else { - bool ok; - width = next.toInt(&ok); - - if (!ok) { - std::cerr << "qmlmin: argument to '-w' is invalid" << std::endl; - return EXIT_FAILURE; - } - - ++index; // consume the next argument - } - } else if (arg.startsWith(QLatin1String("-w"))) { - bool ok; - width = arg.midRef(2).toInt(&ok); - - if (!ok) { - std::cerr << "qmlmin: argument to '-w' is invalid" << std::endl; - return EXIT_FAILURE; - } - } else { - const bool isInvalidOpt = arg.startsWith(QLatin1Char('-')); - if (! isInvalidOpt && fileName.isEmpty()) - fileName = arg; - else { - usage(/*show help*/ isInvalidOpt); - if (isInvalidOpt) - std::cerr << "qmlmin: invalid option '" << qPrintable(arg) << '\'' << std::endl; - else - std::cerr << "qmlmin: too many input files specified" << std::endl; - return EXIT_FAILURE; - } - } - } - - if (fileName.isEmpty()) { - usage(); - return 0; - } - - std::cerr << "qmlmin: This tool is deprecated and will be removed in Qt 6. It is not needed anymore due to QtQml's built-in caching." << std::endl; - - QFile file(fileName); - if (! file.open(QFile::ReadOnly)) { - std::cerr << "qmlmin: '" << qPrintable(fileName) << "' no such file or directory" << std::endl; - return EXIT_FAILURE; - } - - const QString code = QString::fromUtf8(file.readAll()); // QML files are UTF-8 encoded. - file.close(); - - QQmlJS::Minify minify(width); - if (! minify(fileName, code)) { - std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "' (not a valid QML/JS file)" << std::endl; - return EXIT_FAILURE; - } - - // - // verify the output - // - QQmlJS::Minify secondMinify(width); - if (! secondMinify(fileName, minify.minifiedCode()) || secondMinify.minifiedCode() != minify.minifiedCode()) { - std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << '\'' << std::endl; - return EXIT_FAILURE; - } - - QQmlJS::Tokenize originalTokens, minimizedTokens; - originalTokens(fileName, code); - minimizedTokens(fileName, minify.minifiedCode()); - - if (originalTokens.tokenStream().size() != minimizedTokens.tokenStream().size()) { - std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << '\'' << std::endl; - return EXIT_FAILURE; - } - - if (! verifyOnly) { - if (outputFile.isEmpty()) { - const QByteArray chars = minify.minifiedCode().toUtf8(); - std::cout << chars.constData(); - } else { - QFile file(outputFile); - if (! file.open(QFile::WriteOnly)) { - std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "' (permission denied)" << std::endl; - return EXIT_FAILURE; - } - - file.write(minify.minifiedCode().toUtf8()); - file.close(); - } - } - - return 0; -} - -QT_END_NAMESPACE - -int main(int argc, char **argv) -{ - return QT_PREPEND_NAMESPACE(runQmlmin(argc, argv)); -} diff --git a/tools/qmlmin/qmlmin.pro b/tools/qmlmin/qmlmin.pro deleted file mode 100644 index 32d9e3343b..0000000000 --- a/tools/qmlmin/qmlmin.pro +++ /dev/null @@ -1,7 +0,0 @@ -option(host_build) -QT = core qmldevtools-private -SOURCES += main.cpp - -QMAKE_TARGET_DESCRIPTION = QML/JS Minifier - -load(qt_tool) diff --git a/tools/tools.pro b/tools/tools.pro index e2e4e6b0a0..d16f78071c 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -4,7 +4,6 @@ QT_FOR_CONFIG += qml-private qtConfig(qml-devtools) { SUBDIRS += \ qmllint \ - qmlmin \ qmlimportscanner \ qmlformat @@ -45,7 +44,7 @@ qtConfig(qml-devtools) { qmleasing.depends = qmlimportscanner } -# qmlmin, qmlimportscanner & qmlcachegen are build tools. +# qmlimportscanner & qmlcachegen are build tools. # qmlscene is needed by the autotests. # qmltestrunner may be useful for manual testing. # qmlplugindump cannot be a build tool, because it loads target plugins. -- cgit v1.2.3