diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2019-11-11 17:35:09 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-01-13 16:54:13 +0100 |
commit | 6c68a39a56299c72da30a4bac3ca7fe8420687a4 (patch) | |
tree | b88b33e57c71f131dec7adfd4cf42b87766f1382 /tools | |
parent | d0b2a3b5eb4021fb19b634e550cbc6f6664ad775 (diff) |
qmllint: Resolve aliases
Change-Id: Ida53af9774dc72559395064169113d0ee1f47f24
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmllint/findunqualified.cpp | 159 | ||||
-rw-r--r-- | tools/qmllint/findunqualified.h | 3 | ||||
-rw-r--r-- | tools/qmllint/importedmembersvisitor.cpp | 155 | ||||
-rw-r--r-- | tools/qmllint/importedmembersvisitor.h | 73 | ||||
-rw-r--r-- | tools/qmllint/metatypes.h | 6 | ||||
-rw-r--r-- | tools/qmllint/qmllint.pro | 2 | ||||
-rw-r--r-- | tools/qmllint/typedescriptionreader.cpp | 2 |
7 files changed, 272 insertions, 128 deletions
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<UiObjectDefinition *>(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<UiPublicMember *>(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<UiSourceElement *>(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<VariableStatement *>(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<FunctionDeclaration *>(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<QQmlJS::AST::Program *>(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<QQmlJS::AST::Program *>(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<ExpressionStatement *>(publicMember->statement); + if (const auto idExpression = cast<IdentifierExpression *>(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<VariableStatement *>(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<ExpressionStatement *>(scriptBinding->statement); + const auto *idExprension = cast<IdentifierExpression *>(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 <private/qqmljsast_p.h> + +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<ScopeTree::Ptr> m_currentObjects; + ScopeTree::ConstPtr m_rootObject; + QHash<QString, ScopeTree::Ptr> 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) |