aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorLeander Beernaert <leander.beernaert@qt.io>2020-01-16 16:25:06 +0100
committerLeander Beernaert <leander.beernaert@qt.io>2020-01-16 16:25:06 +0100
commit1d333d3375874efb8d37df37dc5ef561573794ad (patch)
tree2d8c995f64c05c84c1fcceb2c5cb40fcae69855f /tools
parentb106d86c433706928b0b0c206a0d9f831681e1bf (diff)
parente79a2658cde899d6ee11ec3c0d0a3768eb2c864b (diff)
Merge remote-tracking branch 'origin/dev' into wip/cmake
Diffstat (limited to 'tools')
-rw-r--r--tools/qml/main.cpp2
-rw-r--r--tools/qml/qml.pro2
-rw-r--r--tools/qmlcachegen/qtquickcompiler.prf3
-rw-r--r--tools/qmlformat/commentastvisitor.cpp270
-rw-r--r--tools/qmlformat/commentastvisitor.h131
-rw-r--r--tools/qmlformat/dumpastvisitor.cpp986
-rw-r--r--tools/qmlformat/dumpastvisitor.h123
-rw-r--r--tools/qmlformat/main.cpp164
-rw-r--r--tools/qmlformat/qmlformat.pro17
-rw-r--r--tools/qmlformat/restructureastvisitor.cpp178
-rw-r--r--tools/qmlformat/restructureastvisitor.h50
-rw-r--r--tools/qmllint/componentversion.cpp63
-rw-r--r--tools/qmllint/componentversion.h43
-rw-r--r--tools/qmllint/fakemetaobject.cpp594
-rw-r--r--tools/qmllint/fakemetaobject.h235
-rw-r--r--tools/qmllint/findunqualified.cpp757
-rw-r--r--tools/qmllint/findunqualified.h87
-rw-r--r--tools/qmllint/importedmembersvisitor.cpp155
-rw-r--r--tools/qmllint/importedmembersvisitor.h73
-rw-r--r--tools/qmllint/main.cpp66
-rw-r--r--tools/qmllint/metatypes.h155
-rw-r--r--tools/qmllint/qcoloroutput.cpp157
-rw-r--r--tools/qmllint/qcoloroutput.h (renamed from tools/qmllint/qcoloroutput_p.h)23
-rw-r--r--tools/qmllint/qmljstypedescriptionreader.h103
-rw-r--r--tools/qmllint/qmllint.pro17
-rw-r--r--tools/qmllint/scopetree.cpp435
-rw-r--r--tools/qmllint/scopetree.h190
-rw-r--r--tools/qmllint/typedescriptionreader.cpp (renamed from tools/qmllint/qmljstypedescriptionreader.cpp)359
-rw-r--r--tools/qmllint/typedescriptionreader.h105
-rw-r--r--tools/qmlmin/main.cpp700
-rw-r--r--tools/qmlmin/qmlmin.pro7
-rw-r--r--tools/qmlplugindump/main.cpp11
-rw-r--r--tools/tools.pro9
33 files changed, 3688 insertions, 2582 deletions
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<LoaderApplication *>(app.get())->startTimer(FILE_OPEN_EVENT_WAIT_TIME);
else
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
}
diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf
index a31a7f5714..8aac3b9e07 100644
--- a/tools/qmlcachegen/qtquickcompiler.prf
+++ b/tools/qmlcachegen/qtquickcompiler.prf
@@ -27,7 +27,8 @@ defineReplace(qmlCacheResourceFileOutputName) {
}
# Flatten RESOURCES that may contain individual files or objects
-load(resources)
+load(resources_functions)
+qtFlattenResources()
NEWRESOURCES =
QMLCACHE_RESOURCE_FILES =
diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp
new file mode 100644
index 0000000000..8264a6099e
--- /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<Comment> 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<SourceLocation> CommentAstVisitor::findCommentsInLine(quint32 line, bool includePrevious) const
+{
+ QList<SourceLocation> 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<Comment> CommentAstVisitor::findOrphanComments(Node *node) const
+{
+ QVector<Comment> 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, Comment::Front | Comment::Back);
+ return true;
+}
+
+bool CommentAstVisitor::visit(UiObjectDefinition *node)
+{
+ attachComment(node, Comment::Front | Comment::Back);
+ 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..369784a5ba
--- /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 <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+
+#include <QHash>
+#include <QString>
+#include <QVector>
+
+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 = Front;
+
+ Comment() = default;
+ Comment(const QQmlJS::Engine *engine, Location location, QList<SourceLocation> srcLocations)
+ : m_location(location), m_srcLocations(srcLocations) {
+ for (const auto& srcLoc : srcLocations) {
+ m_text += engine->code().mid(static_cast<int>(srcLoc.begin()),
+ static_cast<int>(srcLoc.end() - srcLoc.begin())) + "\n";
+ }
+
+ m_text.chop(1);
+ }
+
+ QList<SourceLocation> 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<Node *, Comment> attachedComments() const { return m_attachedComments; }
+ const QHash<quint32, Comment> listComments() const { return m_listItemComments; }
+ const QHash<Node *, QVector<Comment>> 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<SourceLocation> 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<Comment> findOrphanComments(Node *node) const;
+ void attachComment(Node *node, int locations = Comment::DefaultLocations);
+
+ QQmlJS::Engine *m_engine;
+ QHash<Node *, Comment> m_attachedComments;
+ QHash<quint32, Comment> m_listItemComments;
+ QHash<Node *, QVector<Comment>> m_orphanComments;
+};
+
+#endif // COMMENTASTVISITOR_H
diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp
new file mode 100644
index 0000000000..716be0a9a7
--- /dev/null
+++ b/tools/qmlformat/dumpastvisitor.cpp
@@ -0,0 +1,986 @@
+/****************************************************************************
+**
+** 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);
+
+ // 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)
+{
+ 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 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 = "";
+
+ 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<ArrayPattern *>(expression)->elements)+"]";
+ case Node::Kind_IdentifierExpression:
+ return cast<IdentifierExpression*>(expression)->name.toString();
+ case Node::Kind_FieldMemberExpression: {
+ auto *fieldMemberExpr = cast<FieldMemberExpression *>(expression);
+ return parseExpression(fieldMemberExpr->base) + "." + fieldMemberExpr->name.toString();
+ }
+ case Node::Kind_ArrayMemberExpression: {
+ auto *arrayMemberExpr = cast<ArrayMemberExpression *>(expression);
+ return parseExpression(arrayMemberExpr->base)
+ + "[" + parseExpression(arrayMemberExpr->expression) + "]";
+ }
+ case Node::Kind_NestedExpression:
+ return "("+parseExpression(cast<NestedExpression *>(expression)->expression)+")";
+ case Node::Kind_TrueLiteral:
+ return "true";
+ case Node::Kind_FalseLiteral:
+ return "false";
+ case Node::Kind_FunctionExpression:
+ {
+ auto *functExpr = cast<FunctionExpression *>(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<PostIncrementExpression *>(expression)->base)+"++";
+ case Node::Kind_PreIncrementExpression:
+ return "++"+parseExpression(cast<PreIncrementExpression *>(expression)->expression);
+ case Node::Kind_PostDecrementExpression:
+ return parseExpression(cast<PostDecrementExpression *>(expression)->base)+"--";
+ case Node::Kind_PreDecrementExpression:
+ return "--"+parseExpression(cast<PreDecrementExpression *>(expression)->expression);
+ case Node::Kind_NumericLiteral:
+ return QString::number(cast<NumericLiteral *>(expression)->value);
+ case Node::Kind_StringLiteral:
+ return escapeString(cast<StringLiteral *>(expression)->value.toString());
+ 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<CallExpression *>(expression);
+
+ return parseExpression(callExpr->base) + "(" + parseArgumentList(callExpr->arguments) + ")";
+ }
+ case Node::Kind_NewExpression:
+ return "new "+parseExpression(cast<NewExpression *>(expression)->expression);
+ case Node::Kind_NewMemberExpression: {
+ auto *newMemberExpression = cast<NewMemberExpression *>(expression);
+ return "new "+parseExpression(newMemberExpression->base)
+ + "(" +parseArgumentList(newMemberExpression->arguments)+")";
+ }
+ case Node::Kind_DeleteExpression:
+ return "delete " + parseExpression(cast<DeleteExpression *>(expression)->expression);
+ case Node::Kind_VoidExpression:
+ return "void " + parseExpression(cast<VoidExpression *>(expression)->expression);
+ case Node::Kind_TypeOfExpression:
+ return "typeof " + parseExpression(cast<TypeOfExpression *>(expression)->expression);
+ case Node::Kind_UnaryPlusExpression:
+ return "+" + parseExpression(cast<UnaryPlusExpression *>(expression)->expression);
+ case Node::Kind_UnaryMinusExpression:
+ return "-" + parseExpression(cast<UnaryMinusExpression *>(expression)->expression);
+ case Node::Kind_NotExpression:
+ return "!" + parseExpression(cast<NotExpression *>(expression)->expression);
+ case Node::Kind_TildeExpression:
+ return "~" + parseExpression(cast<TildeExpression *>(expression)->expression);
+ case Node::Kind_ConditionalExpression: {
+ auto *condExpr = cast<ConditionalExpression *>(expression);
+
+ QString result = "";
+
+ result += parseExpression(condExpr->expression) + " ? ";
+ result += parseExpression(condExpr->ok) + " : ";
+ result += parseExpression(condExpr->ko);
+
+ return result;
+ }
+ case Node::Kind_YieldExpression: {
+ auto *yieldExpr = cast<YieldExpression*>(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 == nullptr || 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 && 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<ExpressionStatement *>(statement)->expression);
+ case Node::Kind_VariableStatement:
+ return parseVariableDeclarationList(cast<VariableStatement *>(statement)->declarations);
+ case Node::Kind_ReturnStatement:
+ return "return "+parseExpression(cast<ReturnStatement *>(statement)->expression);
+ case Node::Kind_ContinueStatement:
+ return "continue";
+ case Node::Kind_BreakStatement:
+ return "break";
+ case Node::Kind_SwitchStatement: {
+ auto *switchStatement = cast<SwitchStatement *>(statement);
+
+ QString result = "switch ("+parseExpression(switchStatement->expression)+") ";
+
+ result += parseCaseBlock(switchStatement->block);
+
+ return result;
+ }
+ case Node::Kind_IfStatement: {
+ auto *ifStatement = cast<IfStatement *>(statement);
+
+ m_blockNeededBraces = false;
+
+ 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) {
+ ifFalse = parseStatement(ifStatement->ko, false, false);
+ ifTrue = parseStatement(ifStatement->ok, !ifFalse.isEmpty(), false);
+ }
+
+ QString result = "if (" + parseExpression(ifStatement->expression) + ")";
+
+ if (ifTrueBlock) {
+ result += " " + ifTrue;
+ } else {
+ result += "\n";
+ m_indentLevel++;
+ result += formatLine(ifTrue);
+ m_indentLevel--;
+ }
+
+ if (!ifFalse.isEmpty())
+ {
+ 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;
+ }
+ case Node::Kind_ForStatement: {
+ auto *forStatement = cast<ForStatement *>(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<ForEachStatement *>(statement);
+
+ QString result = "for (";
+
+ PatternElement *patternElement = cast<PatternElement *>(forEachStatement->lhs);
+
+ if (patternElement != nullptr)
+ result += parsePatternElement(patternElement);
+ else
+ result += parseExpression(forEachStatement->lhs->expressionCast());
+
+ 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<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<DoWhileStatement *>(statement);
+ return "do " + parseBlock(cast<Block *>(doWhileStatement->statement), true, false)
+ + "while (" + parseExpression(doWhileStatement->expression) + ")";
+ }
+ case Node::Kind_TryStatement: {
+ auto *tryStatement = cast<TryStatement *>(statement);
+
+ Catch *catchExpr = tryStatement->catchExpression;
+ Finally *finallyExpr = tryStatement->finallyExpression;
+
+ QString result;
+
+ result += "try " + parseBlock(cast<Block *>(tryStatement->statement), true, false);
+
+ result += "catch (" + parsePatternElement(catchExpr->patternElement, false) + ") "
+ + parseBlock(cast<Block *>(catchExpr->statement), finallyExpr, false);
+
+ if (finallyExpr) {
+ result += "finally " + parseBlock(cast<Block *>(tryStatement->statement), false, false);
+ }
+
+ return result;
+ }
+ case Node::Kind_Block: {
+ return parseBlock(cast<Block *>(statement), blockHasNext, blockAllowBraceless);
+ }
+ case Node::Kind_ThrowStatement:
+ return "throw "+parseExpression(cast<ThrowStatement *>(statement)->expression);
+ case Node::Kind_LabelledStatement: {
+ auto *labelledStatement = cast<LabelledStatement *>(statement);
+ QString result = labelledStatement->label+":\n";
+ result += formatLine(parseStatement(labelledStatement->statement), false);
+
+ return result;
+ }
+ case Node::Kind_WithStatement: {
+ auto *withStatement = cast<WithStatement *>(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 ? "}," : "}");
+ addLine(getComment(node, Comment::Location::Back));
+ 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();
+
+ if (parseUiQualifiedId(node->qualifiedId) != "id")
+ 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;
+
+ 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;
+ }
+
+ 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);
+ else
+ result.prepend(parseUiQualifiedId(node->qualifiedId) + ": ");
+
+ addNewLine();
+ addLine(getComment(node, Comment::Location::Front));
+ addLine(result+" {");
+
+ m_indentLevel++;
+
+ return true;
+}
+
+void DumpAstVisitor::endVisit(UiObjectBinding *node) {
+ m_indentLevel--;
+ addLine("}");
+ addLine(getComment(node, Comment::Location::Back));
+
+ addNewLine();
+}
+
+bool DumpAstVisitor::visit(UiImport *node) {
+ addLine(getComment(node, Comment::Location::Front));
+
+ QString result = "import ";
+
+ if (!node->fileName.isEmpty())
+ result += escapeString(node->fileName.toString());
+ 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..e73a7b628f
--- /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 <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+
+#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 = false);
+ 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 <QCoreApplication>
+#include <QFile>
+
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+
+#if QT_CONFIG(commandlineparser)
+#include <QCommandLineParser>
+#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 <QList>
+
+RestructureAstVisitor::RestructureAstVisitor(Node *rootNode, bool sortImports) : m_sortImports(sortImports)
+{
+ rootNode->accept(this);
+}
+
+template<typename T>
+static QList<T *> findKind(UiObjectMemberList *list)
+{
+ QList<T *> members;
+ for (auto *item = list; item != nullptr; item = item->next) {
+ if (cast<T *>(item->member) != nullptr)
+ members.append(cast<T *>(item->member));
+ }
+
+ return members;
+}
+
+template<typename T>
+static QList<T *> findKind(UiHeaderItemList *list)
+{
+ QList<T *> members;
+ for (auto *item = list; item != nullptr; item = item->next) {
+ if (cast<T *>(item->headerItem) != nullptr)
+ members.append(cast<T *>(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<Node *> correctOrder;
+
+ auto imports = findKind<UiImport>(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<UiObjectMember*> correctOrder;
+
+ auto enumDeclarations = findKind<UiEnumDeclaration>(node);
+ auto scriptBindings = findKind<UiScriptBinding>(node);
+ auto arrayBindings = findKind<UiArrayBinding>(node);
+ auto publicMembers = findKind<UiPublicMember>(node);
+ auto sourceElements = findKind<UiSourceElement>(node);
+ auto objectDefinitions = findKind<UiObjectDefinition>(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 <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+
+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/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 <QString>
-#include <QCryptographicHash>
-
-#include <limits>
-
-using namespace LanguageUtils;
-
-const int ComponentVersion::NoVersion = -1;
-const int ComponentVersion::MaxVersion = std::numeric_limits<int>::max();
-
-ComponentVersion::ComponentVersion()
- : _major(NoVersion), _minor(NoVersion)
-{
-}
-
-ComponentVersion::ComponentVersion(int major, int minor)
- : _major(major), _minor(minor)
-{
-}
+#include <QtCore/qstring.h>
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<const char *>(&_major), sizeof(_major));
- hash.addData(reinterpret_cast<const char *>(&_minor), sizeof(_minor));
-}
-
-namespace LanguageUtils {
-
bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs)
{
return lhs.majorVersion() < rhs.majorVersion()
@@ -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 <qglobal.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.
-QT_BEGIN_NAMESPACE
-class QCryptographicHash;
-QT_END_NAMESPACE
-
-namespace LanguageUtils {
+#include <QtCore/qglobal.h>
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 <QCryptographicHash>
-
-using namespace LanguageUtils;
-
-FakeMetaEnum::FakeMetaEnum()
-{}
-
-FakeMetaEnum::FakeMetaEnum(const QString &name)
- : m_name(name)
-{}
-
-bool FakeMetaEnum::isValid() const
-{ return !m_name.isEmpty(); }
-
-QString FakeMetaEnum::name() const
-{ return m_name; }
-
-void FakeMetaEnum::setName(const QString &name)
-{ m_name = name; }
-
-void FakeMetaEnum::addKey(const QString &key)
-{ 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<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_name.constData()), len * sizeof(QChar));
- len = m_keys.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- foreach (const QString &key, m_keys) {
- len = key.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar));
- }
-}
-
-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<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_name.constData()), len * sizeof(QChar));
- hash.addData(reinterpret_cast<const char *>(&m_methodAccess), sizeof(m_methodAccess));
- hash.addData(reinterpret_cast<const char *>(&m_methodTy), sizeof(m_methodTy));
- hash.addData(reinterpret_cast<const char *>(&m_revision), sizeof(m_revision));
- len = m_paramNames.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- foreach (const QString &pName, m_paramNames) {
- len = pName.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(pName.constData()), len * sizeof(QChar));
- }
- len = m_paramTypes.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- foreach (const QString &pType, m_paramTypes) {
- len = pType.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(pType.constData()), len * sizeof(QChar));
- }
- len = m_returnType.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_returnType.constData()), len * sizeof(QChar));
-}
-
-QString FakeMetaMethod::describe(int baseIndent) const
-{
- QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
- QString res = QLatin1String("Method {");
- res += newLine;
- res += QLatin1String(" methodName:");
- res += methodName();
- res += newLine;
- res += QLatin1String(" methodType:");
- res += methodType();
- res += newLine;
- res += QLatin1String(" parameterNames:[");
- foreach (const QString &pName, parameterNames()) {
- res += newLine;
- res += QLatin1String(" ");
- res += pName;
- }
- res += QLatin1Char(']');
- res += newLine;
- res += QLatin1String(" parameterTypes:[");
- foreach (const QString &pType, parameterTypes()) {
- res += newLine;
- res += QLatin1String(" ");
- res += pType;
- }
- res += QLatin1Char(']');
- res += newLine;
- res += QLatin1Char('}');
- return res;
-}
-
-QString FakeMetaMethod::toString() const
-{
- return describe();
-}
-
-
-FakeMetaProperty::FakeMetaProperty(const QString &name, const QString &type, bool isList,
- bool isWritable, bool isPointer, int revision)
- : m_propertyName(name)
- , m_type(type)
- , m_isList(isList)
- , m_isWritable(isWritable)
- , m_isPointer(isPointer)
- , m_revision(revision)
-{}
-
-QString FakeMetaProperty::name() const
-{ return m_propertyName; }
-
-QString FakeMetaProperty::typeName() const
-{ return m_type; }
-
-bool FakeMetaProperty::isList() const
-{ return m_isList; }
-
-bool FakeMetaProperty::isWritable() const
-{ return m_isWritable; }
-
-bool FakeMetaProperty::isPointer() const
-{ return m_isPointer; }
-
-int FakeMetaProperty::revision() const
-{ return m_revision; }
-
-void FakeMetaProperty::addToHash(QCryptographicHash &hash) const
-{
- int len = m_propertyName.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_propertyName.constData()), len * sizeof(QChar));
- hash.addData(reinterpret_cast<const char *>(&m_revision), sizeof(m_revision));
- int flags = (m_isList ? (1 << 0) : 0)
- + (m_isPointer ? (1 << 1) : 0)
- + (m_isWritable ? (1 << 2) : 0);
- hash.addData(reinterpret_cast<const char *>(&flags), sizeof(flags));
- len = m_type.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_type.constData()), len * sizeof(QChar));
-}
-
-QString FakeMetaProperty::describe(int baseIndent) const
-{
- auto boolStr = [] (bool v) { return v ? QLatin1String("true") : QLatin1String("false"); };
- QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
- QString res = QLatin1String("Property {");
- res += newLine;
- res += QLatin1String(" name:");
- res += name();
- res += newLine;
- res += QLatin1String(" typeName:");
- res += typeName();
- res += newLine;
- res += QLatin1String(" typeName:");
- res += QString::number(revision());
- res += newLine;
- res += QLatin1String(" isList:");
- res += boolStr(isList());
- res += newLine;
- res += QLatin1String(" isPointer:");
- res += boolStr(isPointer());
- res += newLine;
- res += QLatin1String(" isWritable:");
- res += boolStr(isWritable());
- res += newLine;
- res += QLatin1Char('}');
- return res;
-}
-
-QString FakeMetaProperty::toString() const
-{
- return describe();
-}
-
-
-FakeMetaObject::FakeMetaObject() : m_isSingleton(false), m_isCreatable(true), m_isComposite(false)
-{
-}
-
-QString FakeMetaObject::className() const
-{ return m_className; }
-void FakeMetaObject::setClassName(const QString &name)
-{ m_className = name; }
-
-void FakeMetaObject::addExport(const QString &name, const QString &package, ComponentVersion version)
-{
- Export exp;
- exp.type = name;
- exp.package = package;
- exp.version = version;
- m_exports.append(exp);
-}
-
-void FakeMetaObject::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision)
-{
- m_exports[exportIndex].metaObjectRevision = metaObjectRevision;
-}
-
-QList<FakeMetaObject::Export> FakeMetaObject::exports() const
-{ return m_exports; }
-FakeMetaObject::Export FakeMetaObject::exportInPackage(const QString &package) const
-{
- foreach (const Export &exp, m_exports) {
- if (exp.package == package)
- return exp;
- }
- return Export();
-}
-
-void FakeMetaObject::setSuperclassName(const QString &superclass)
-{ m_superName = superclass; }
-QString FakeMetaObject::superclassName() const
-{ return m_superName; }
-
-void FakeMetaObject::addEnum(const FakeMetaEnum &fakeEnum)
-{ m_enumNameToIndex.insert(fakeEnum.name(), m_enums.size()); m_enums.append(fakeEnum); }
-int FakeMetaObject::enumeratorCount() const
-{ return m_enums.size(); }
-int FakeMetaObject::enumeratorOffset() const
-{ return 0; }
-FakeMetaEnum FakeMetaObject::enumerator(int index) const
-{ return m_enums.at(index); }
-int FakeMetaObject::enumeratorIndex(const QString &name) const
-{ return m_enumNameToIndex.value(name, -1); }
-
-void FakeMetaObject::addProperty(const FakeMetaProperty &property)
-{ m_propNameToIdx.insert(property.name(), m_props.size()); m_props.append(property); }
-int FakeMetaObject::propertyCount() const
-{ return m_props.size(); }
-int FakeMetaObject::propertyOffset() const
-{ return 0; }
-FakeMetaProperty FakeMetaObject::property(int index) const
-{ return m_props.at(index); }
-int FakeMetaObject::propertyIndex(const QString &name) const
-{ return m_propNameToIdx.value(name, -1); }
-
-void FakeMetaObject::addMethod(const FakeMetaMethod &method)
-{ m_methods.append(method); }
-int FakeMetaObject::methodCount() const
-{ return m_methods.size(); }
-int FakeMetaObject::methodOffset() const
-{ return 0; }
-FakeMetaMethod FakeMetaObject::method(int index) const
-{ return m_methods.at(index); }
-int FakeMetaObject::methodIndex(const QString &name) const //If performances becomes an issue, just use a nameToIdx hash
-{
- for (int i=0; i<m_methods.count(); i++)
- if (m_methods[i].methodName() == name)
- return i;
- return -1;
-}
-
-QString FakeMetaObject::defaultPropertyName() const
-{ return m_defaultPropertyName; }
-void FakeMetaObject::setDefaultPropertyName(const QString &defaultPropertyName)
-{ m_defaultPropertyName = defaultPropertyName; }
-
-QString FakeMetaObject::attachedTypeName() const
-{ return m_attachedTypeName; }
-void FakeMetaObject::setAttachedTypeName(const QString &name)
-{ m_attachedTypeName = name; }
-
-QByteArray FakeMetaObject::calculateFingerprint() const
-{
- QCryptographicHash hash(QCryptographicHash::Sha1);
- int len = m_className.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_className.constData()), len * sizeof(QChar));
- len = m_attachedTypeName.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_attachedTypeName.constData()), len * sizeof(QChar));
- len = m_defaultPropertyName.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_defaultPropertyName.constData()), len * sizeof(QChar));
- len = m_enumNameToIndex.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- {
- QStringList keys(m_enumNameToIndex.keys());
- keys.sort();
- foreach (const QString &key, keys) {
- len = key.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar));
- int value = m_enumNameToIndex.value(key);
- hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint...
- m_enums.at(value).addToHash(hash);
- }
- }
- len = m_exports.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- foreach (const Export &e, m_exports)
- e.addToHash(hash); // normalize order?
- len = m_exports.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- foreach (const FakeMetaMethod &m, m_methods)
- m.addToHash(hash); // normalize order?
- {
- QStringList keys(m_propNameToIdx.keys());
- keys.sort();
- foreach (const QString &key, keys) {
- len = key.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar));
- int value = m_propNameToIdx.value(key);
- hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint...
- m_props.at(value).addToHash(hash);
- }
- }
- len = m_superName.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(m_superName.constData()), len * sizeof(QChar));
-
- QByteArray res = hash.result();
- res.append('F');
- return res;
-}
-
-void FakeMetaObject::updateFingerprint()
-{
- m_fingerprint = calculateFingerprint();
-}
-
-QByteArray FakeMetaObject::fingerprint() const
-{
- return m_fingerprint;
-}
-
-bool FakeMetaObject::isSingleton() const
-{
- return m_isSingleton;
-}
-
-bool FakeMetaObject::isCreatable() const
-{
- return m_isCreatable;
-}
-
-bool FakeMetaObject::isComposite() const
-{
- return m_isComposite;
-}
-
-void FakeMetaObject::setIsSingleton(bool value)
-{
- m_isSingleton = value;
-}
-
-void FakeMetaObject::setIsCreatable(bool value)
-{
- m_isCreatable = value;
-}
-
-void FakeMetaObject::setIsComposite(bool value)
-{
- m_isSingleton = value;
-}
-
-QString FakeMetaObject::toString() const
-{
- return describe();
-}
-
-QString FakeMetaObject::describe(bool printDetails, int baseIndent) const
-{
- QString res = QString::fromLatin1("FakeMetaObject@%1")
- .arg((quintptr)(void *)this, 0, 16);
- if (!printDetails)
- return res;
- auto boolStr = [] (bool v) { return v ? QLatin1String("true") : QLatin1String("false"); };
- QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
- res += QLatin1Char('{');
- res += newLine;
- res += QLatin1String("className:");
- res += className();
- res += newLine;
- res += QLatin1String("superClassName:");
- res += superclassName();
- res += newLine;
- res += QLatin1String("isSingleton:");
- res += boolStr(isSingleton());
- res += newLine;
- res += QLatin1String("isCreatable:");
- res += boolStr(isCreatable());
- res += newLine;
- res += QLatin1String("isComposite:");
- res += boolStr(isComposite());
- res += newLine;
- res += QLatin1String("defaultPropertyName:");
- res += defaultPropertyName();
- res += newLine;
- res += QLatin1String("attachedTypeName:");
- res += attachedTypeName();
- res += newLine;
- res += QLatin1String("fingerprint:");
- res += QString::fromUtf8(fingerprint());
-
- res += newLine;
- res += QLatin1String("exports:[");
- foreach (const Export &e, exports()) {
- res += newLine;
- res += QLatin1String(" ");
- res += e.describe(baseIndent + 2);
- }
- res += QLatin1Char(']');
-
- res += newLine;
- res += QLatin1String("enums:[");
- for (int iEnum = 0; iEnum < enumeratorCount() ; ++ iEnum) {
- FakeMetaEnum e = enumerator(enumeratorOffset() + iEnum);
- res += newLine;
- res += QLatin1String(" ");
- res += e.describe(baseIndent + 2);
- }
- res += QLatin1Char(']');
-
- res += newLine;
- res += QLatin1String("properties:[");
- for (int iProp = 0; iProp < propertyCount() ; ++ iProp) {
- FakeMetaProperty prop = property(propertyOffset() + iProp);
- res += newLine;
- res += QLatin1String(" ");
- res += prop.describe(baseIndent + 2);
- }
- res += QLatin1Char(']');
- res += QLatin1String("methods:[");
- for (int iMethod = 0; iMethod < methodOffset() ; ++ iMethod) {
- FakeMetaMethod m = method(methodOffset() + iMethod);
- res += newLine;
- res += QLatin1String(" ");
- m.describe(baseIndent + 2);
- }
- res += QLatin1Char(']');
- res += newLine;
- res += QLatin1Char('}');
- return res;
-}
-
-FakeMetaObject::Export::Export()
- : metaObjectRevision(0)
-{}
-bool FakeMetaObject::Export::isValid() const
-{ return version.isValid() || !package.isEmpty() || !type.isEmpty(); }
-
-void FakeMetaObject::Export::addToHash(QCryptographicHash &hash) const
-{
- int len = package.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(package.constData()), len * sizeof(QChar));
- len = type.size();
- hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
- hash.addData(reinterpret_cast<const char *>(type.constData()), len * sizeof(QChar));
- version.addToHash(hash);
- hash.addData(reinterpret_cast<const char *>(&metaObjectRevision), sizeof(metaObjectRevision));
-}
-
-QString FakeMetaObject::Export::describe(int baseIndent) const
-{
- QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent);
- QString res = QLatin1String("Export {");
- res += newLine;
- res += QLatin1String(" package:");
- res += package;
- res += newLine;
- res += QLatin1String(" type:");
- res += type;
- res += newLine;
- res += QLatin1String(" version:");
- res += version.toString();
- res += newLine;
- res += QLatin1String(" metaObjectRevision:");
- res += QString::number(metaObjectRevision);
- res += newLine;
- res += QLatin1String(" isValid:");
- res += QString::number(isValid());
- res += newLine;
- res += QLatin1Char('}');
- return res;
-}
-
-QString FakeMetaObject::Export::toString() const
-{
- return describe();
-}
diff --git a/tools/qmllint/fakemetaobject.h b/tools/qmllint/fakemetaobject.h
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 <QString>
-#include <QStringList>
-#include <QList>
-#include <QHash>
-#include <QSharedPointer>
-
-#include "componentversion.h"
-
-QT_BEGIN_NAMESPACE
-class QCryptographicHash;
-QT_END_NAMESPACE
-
-namespace LanguageUtils {
-
-class FakeMetaEnum {
- QString m_name;
- QStringList m_keys;
-
-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<FakeMetaObject> Ptr;
- typedef QSharedPointer<const FakeMetaObject> ConstPtr;
-
- class Export {
- public:
- Export();
-
- QString package;
- QString type;
- ComponentVersion version;
- int metaObjectRevision;
-
- bool isValid() const;
- void addToHash(QCryptographicHash &hash) const;
-
- QString describe(int baseIndent = 0) const;
- QString toString() const;
- };
-
-private:
- QString m_className;
- QList<Export> m_exports;
- QString m_superName;
- QList<FakeMetaEnum> m_enums;
- QHash<QString, int> m_enumNameToIndex;
- QList<FakeMetaProperty> m_props;
- QHash<QString, int> m_propNameToIdx;
- QList<FakeMetaMethod> m_methods;
- QString m_defaultPropertyName;
- QString m_attachedTypeName;
- QByteArray m_fingerprint;
- bool m_isSingleton;
- bool m_isCreatable;
- bool m_isComposite;
-
-public:
- FakeMetaObject();
-
- QString className() const;
- void setClassName(const QString &name);
-
- void addExport(const QString &name, const QString &package, ComponentVersion version);
- void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision);
- QList<Export> exports() const;
- Export exportInPackage(const QString &package) const;
-
- void setSuperclassName(const QString &superclass);
- QString superclassName() const;
-
- void addEnum(const FakeMetaEnum &fakeEnum);
- int enumeratorCount() const;
- int enumeratorOffset() const;
- FakeMetaEnum enumerator(int index) const;
- int enumeratorIndex(const QString &name) const;
-
- void addProperty(const FakeMetaProperty &property);
- int propertyCount() const;
- int propertyOffset() const;
- FakeMetaProperty property(int index) const;
- int propertyIndex(const QString &name) const;
-
- void addMethod(const FakeMetaMethod &method);
- int methodCount() const;
- int methodOffset() const;
- FakeMetaMethod method(int index) const;
- int methodIndex(const QString &name) const; // Note: Returns any method with that name in case of overloads
-
- QString defaultPropertyName() const;
- void setDefaultPropertyName(const QString &defaultPropertyName);
-
- QString attachedTypeName() const;
- void setAttachedTypeName(const QString &name);
- QByteArray calculateFingerprint() const;
- void updateFingerprint();
- QByteArray fingerprint() const;
-
- bool isSingleton() const;
- bool isCreatable() const;
- bool isComposite() const;
- void setIsSingleton(bool value);
- void setIsCreatable(bool value);
- void setIsComposite(bool value);
-
- QString describe(bool printDetails = true, int baseIndent = 0) const;
- QString toString() const;
-};
-
-} // namespace LanguageUtils
-
-#endif // FAKEMETAOBJECT_H
diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp
index d72f02b94a..807110c3c1 100644
--- a/tools/qmllint/findunqualified.cpp
+++ b/tools/qmllint/findunqualified.cpp
@@ -27,19 +27,24 @@
****************************************************************************/
#include "findunqualified.h"
+#include "importedmembersvisitor.h"
#include "scopetree.h"
+#include "typedescriptionreader.h"
-#include "qmljstypedescriptionreader.h"
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qv4codegen_p.h>
+#include <QtQml/private/qqmldirparser_p.h>
-#include <QFile>
-#include <QDirIterator>
-#include <QScopedValueRollback>
+#include <QtCore/qfile.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qscopedvaluerollback.h>
-#include <private/qqmljsast_p.h>
-#include <private/qqmljslexer_p.h>
-#include <private/qqmljsparser_p.h>
-#include <private/qv4codegen_p.h>
-#include <private/qqmldirparser_p.h>
+static const QString prefixedName(const QString &prefix, const QString &name)
+{
+ return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
+}
static QQmlDirParser createQmldirParserForFile(const QString &filename)
{
@@ -50,17 +55,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)
+void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, const QString &name)
{
- m_currentScope = m_currentScope->createNewChildScope(type, name);
+ m_currentScope = m_currentScope->createNewChildScope(type, name).get();
}
void FindUnqualifiedIDVisitor::leaveEnvironment()
@@ -68,9 +73,53 @@ 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<UiImport *>(header->headerItem)) {
+ if (import->version) {
+ QString path;
+ auto uri = import->importUri;
+ while (uri) {
+ path.append(uri->name);
+ path.append("/");
+ uri = uri->next;
+ }
+ path.chop(1);
+ QString prefix = QLatin1String("");
+ if (import->asToken.isValid()) {
+ prefix += import->importId + QLatin1Char('.');
+ }
+ importHelper(path, prefix, import->version->majorVersion,
+ import->version->minorVersion);
+ }
+ }
+ header = header->next;
+ }
+}
+
+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(), "");
+ result->addMethod(method);
+ }
+ }
+ return result;
+}
+
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,21 +128,22 @@ 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)
{
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<QStringRef> &refs, const QChar &sep)
- {
+ auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep) {
QString str;
for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
if (it != refs.cbegin())
@@ -103,292 +153,242 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths
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<ImportVersion>(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, QString prefix, int major, int minor)
-{
- QPair<QString, QString> importId { id, prefix };
- if (m_alreadySeenImports.contains(importId)) {
- return;
- } else {
- m_alreadySeenImports.insert(importId);
- }
- id = id.replace(QLatin1String("/"), QLatin1String("."));
- auto qmltypesPaths = completeImportPaths(id, m_qmltypeDirs, major, minor);
+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");
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
- QList<QQmlJS::ModuleApiInfo> 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<QString, LanguageUtils::FakeMetaObject *> 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;
- }
+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());
+}
- auto mo = qmlComponents.find(it.key());
- if (mo == qmlComponents.end())
- mo = qmlComponents.insert(it.key(), localQmlFile2FakeMetaObject(filePath));
+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);
- (*mo)->addExport(
- it.key(), reader.typeNamespace(),
- LanguageUtils::ComponentVersion(it->majorVersion, it->minorVersion));
- }
- for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) {
- objects.insert(it.key(),
- QSharedPointer<const LanguageUtils::FakeMetaObject>(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());
+ QHash<QString, ScopeTree *> 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(), localFile2ScopeTree(filePath));
+
+ (*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);
+ auto const &id = split.at(0);
+ 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
- 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());
+ 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();
+ for (const auto &valExport : exports)
+ m_exportedName2Scope.insert(prefixedName(prefix, valExport.type()), val);
+
+ const auto enums = val->enums();
+ for (const auto &valEnum : enums)
+ m_currentScope->addEnum(valEnum);
+ }
+}
+
+void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString &prefix,
+ int major, int minor)
+{
+ const QString id = QString(module).replace(QLatin1Char('/'), QLatin1Char('.'));
+ QPair<QString, QString> 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);
}
}
}
-LanguageUtils::FakeMetaObject *
-FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath)
+ScopeTree *FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath)
{
using namespace QQmlJS::AST;
- auto fake = new LanguageUtils::FakeMetaObject;
- QString baseName = QFileInfo { filePath }.baseName();
- fake->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName);
+ const QFileInfo info { filePath };
+ QString baseName = info.baseName();
+ 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 isESModule = lowerSuffix == QLatin1String("mjs");
+ const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
+
QFile file(filePath);
if (!file.open(QFile::ReadOnly)) {
- return fake;
+ return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope,
+ scopeName);
}
+
QString code = file.readAll();
file.close();
- QQmlJS::Engine engine;
- QQmlJS::Lexer lexer(&engine);
-
- lexer.setCode(code, 1, true);
+ lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript);
QQmlJS::Parser parser(&engine);
- if (!parser.parse()) {
- return fake;
- }
- QQmlJS::AST::UiProgram *program = parser.ast();
- auto header = program->headers;
- while (header) {
- if (auto import = cast<UiImport *>(header->headerItem)) {
- if (import->version) {
- QString path;
- auto uri = import->importUri;
- while (uri) {
- path.append(uri->name);
- path.append("/");
- uri = uri->next;
- }
- path.chop(1);
- QString prefix = QLatin1String("");
- if (import->asToken.isValid()) {
- prefix += import->importId + QLatin1Char('.');
- }
- importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion);
- }
- }
- header = header->next;
- }
- auto member = program->members;
- // member should be the sole element
- Q_ASSERT(!member->next);
- Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition);
- auto definition = static_cast<UiObjectDefinition *>(member->member);
- auto qualifiedId = definition->qualifiedTypeNameId;
- while (qualifiedId && qualifiedId->next) {
- qualifiedId = qualifiedId->next;
+
+ const bool success = isJavaScript ? (isESModule ? parser.parseModule()
+ : parser.parseProgram())
+ : parser.parse();
+ if (!success) {
+ return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope,
+ scopeName);
}
- fake->setSuperclassName(qualifiedId->name.toString());
- UiObjectMemberList *initMembers = definition->initializer->members;
- while (initMembers) {
- switch (initMembers->member->kind) {
- case UiObjectMember::Kind_UiArrayBinding: {
- // nothing to do
- break;
- }
- case UiObjectMember::Kind_UiEnumDeclaration: {
- // 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 = static_cast<UiPublicMember *>(initMembers->member);
- switch (publicMember->type) {
- case UiPublicMember::Signal: {
- UiParameterList *param = publicMember->parameters;
- LanguageUtils::FakeMetaMethod method;
- method.setMethodType(LanguageUtils::FakeMetaMethod::Signal);
- method.setMethodName(publicMember->name.toString());
- while (param) {
- method.addParameter(param->name.toString(), param->type->name.toString());
- param = param->next;
- }
- fake->addMethod(method);
- break;
- }
- case UiPublicMember::Property: {
- LanguageUtils::FakeMetaProperty fakeprop { publicMember->name.toString(),
- publicMember->typeModifier.toString(),
- false,
- false,
- false,
- 0 };
- fake->addProperty(fakeprop);
- break;
- }
- }
- break;
- }
- case UiObjectMember::Kind_UiScriptBinding: {
- // does not create anything new, ignore
- break;
- }
- case UiObjectMember::Kind_UiSourceElement: {
- auto sourceElement = static_cast<UiSourceElement *>(initMembers->member);
- if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) {
- LanguageUtils::FakeMetaMethod method;
- method.setMethodName(fexpr->name.toString());
- method.setMethodType(LanguageUtils::FakeMetaMethod::Method);
- FormalParameterList *parameters = fexpr->formals;
- while (parameters) {
- method.addParameter(parameters->element->bindingIdentifier.toString(),
- "");
- parameters = parameters->next;
- }
- fake->addMethod(method);
- } else if (ClassExpression *clexpr =
- sourceElement->sourceElement->asClassDefinition()) {
- LanguageUtils::FakeMetaProperty prop {
- clexpr->name.toString(), "", false, false, false, 1
- };
- fake->addProperty(prop);
- } else if (cast<VariableStatement *>(sourceElement->sourceElement)) {
- // nothing to do
- } else {
- 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;
+
+ if (!isJavaScript) {
+ QQmlJS::AST::UiProgram *program = parser.ast();
+ parseHeaders(program->headers);
+ ImportedMembersVisitor membersVisitor(&m_colorOut);
+ program->members->accept(&membersVisitor);
+ return membersVisitor.result(scopeName);
}
- return fake;
+
+ // TODO: Anything special to do with ES modules here?
+ return parseProgram(QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), scopeName);
}
-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()) {
- LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next());
- m_exportedName2MetaObject.insert(
- prefix + fake->className(),
- QSharedPointer<const LanguageUtils::FakeMetaObject>(fake));
+ ScopeTree::ConstPtr scope(localFile2ScopeTree(it.next()));
+ if (!scope->className().isEmpty())
+ m_exportedName2Scope.insert(prefixedName(prefix, scope->className()), 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)
- ? name
- : prefix + QLatin1Char('.') + name];
- if (metaObject) {
- auto propertyCount = metaObject->propertyCount();
- for (auto i = 0; i < propertyCount; ++i) {
- m_currentScope->insertPropertyIdentifier(metaObject->property(i).name());
+ ScopeTree::ConstPtr scope = m_exportedName2Scope.value(m_exportedName2Scope.contains(name)
+ ? name
+ : prefix + QLatin1Char('.') + name);
+ if (scope) {
+ const auto properties = scope->properties();
+ for (auto property : properties) {
+ property.setType(m_exportedName2Scope.value(property.typeName()).get());
+ m_currentScope->insertPropertyIdentifier(property);
}
- m_currentScope->addMethodsFromMetaObject(metaObject);
-
- name = metaObject->superclassName();
- if (name.isEmpty() || name == QLatin1String("QObject")) {
+ 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);
+ m_visitFailed = true;
break;
}
}
@@ -404,8 +404,8 @@ void FindUnqualifiedIDVisitor::throwRecursionDepthError()
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *)
{
enterEnvironment(ScopeType::QMLScope, "program");
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
- QList<QQmlJS::ModuleApiInfo> moduleApis;
+ QHash<QString, ScopeTree::ConstPtr> objects;
+ QList<ModuleApiInfo> moduleApis;
QStringList dependencies;
for (auto const &dir : m_qmltypeDirs) {
QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter,
@@ -418,29 +418,23 @@ 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());
- }
- }
- // 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()] = {};
+ for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) {
+ auto val = objectIt.value();
+ m_types[objectIt.key()] = val;
- // add QML builtins
- m_exportedName2MetaObject["QtObject"] = {}; // QtObject contains nothing of interest
+ const auto exports = val->exports();
+ for (const auto &valExport : exports)
+ m_exportedName2Scope.insert(valExport.type(), val);
- 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 };
+ 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 ScopeTree
+ m_exportedName2Scope.insert(QFileInfo { m_filePath }.baseName(), {});
- importDirectory(".", QString());
+ importFileOrDirectory(".", QString());
return true;
}
@@ -518,7 +512,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 +524,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;
}
@@ -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()) {
@@ -563,13 +563,12 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
auto name = uisb->qualifiedId->name;
if (name == QLatin1String("id")) {
// found id
- auto expstat = static_cast<ExpressionStatement *>(uisb->statement);
- auto identexp = static_cast<IdentifierExpression *>(expstat->expression);
+ auto expstat = cast<ExpressionStatement *>(uisb->statement);
+ auto identexp = cast<IdentifierExpression *>(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_currentScope);
+ if (m_currentScope->isVisualRootScope())
m_rootId = identexp->name.toString();
- }
} else {
const QString signal = signalName(name);
if (signal.isEmpty())
@@ -582,7 +581,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
const auto statement = uisb->statement;
if (statement->kind == Node::Kind::Kind_ExpressionStatement) {
- if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
+ if (cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
// functions are already handled
// they do not get names inserted according to the signal, but access their formal
// parameters
@@ -607,37 +606,44 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm)
{
// property bool inactive: !active
// extract name inactive
- m_currentScope->insertPropertyIdentifier(uipm->name.toString());
+ MetaProperty property(
+ uipm->name.toString(),
+ // TODO: signals, complex types etc.
+ uipm->memberType ? uipm->memberType->name.toString() : QString(),
+ uipm->typeModifier == QLatin1String("list"),
+ !uipm->isReadonlyMember,
+ false,
+ uipm->memberType ? (uipm->memberType->name == QLatin1String("alias")) : 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_exportedName2MetaObject.contains(name.toString())) {
- m_currentScope->addIdToAccssedIfNotInParentScopes(
- { name.toString(), idexp->firstSourceLocation() }, m_unknownImports);
- }
+ m_currentScope->addIdToAccessed(name.toString(), idexp->firstSourceLocation());
+ m_fieldMemberBase = idexp;
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("<id>")),
- 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 +651,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<ScopeTree*> rollback(m_currentScope, outstandingConnection.scope);
outstandingConnection.uiod->initializer->accept(this);
}
- return m_rootScope->recheckIdentifiers(m_code, m_qmlid2meta, m_rootScope.get(), m_rootId, m_colorOut);
+ return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_exportedName2Scope,
+ m_rootScope.get(), m_rootId, m_colorOut);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
@@ -686,18 +696,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("<anon>"));
}
- QString name = fexpr->name.toString();
- if (name.isEmpty())
- name = "<anon>";
- enterEnvironment(ScopeType::JSFunctionScope, name);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
@@ -735,15 +743,17 @@ 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()) {
- 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
+ const QString importId = import->importId.toString();
+ m_qmlid2scope.insert(importId, m_exportedName2Scope.value(importId).get());
}
if (import->version) {
auto uri = import->importUri;
@@ -761,14 +771,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,20 +790,34 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
id = id->next;
}
name.chop(1);
+
+ 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);
+
enterEnvironment(ScopeType::QMLScope, name);
- if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component and QtObject, but they also have no interesting properties
- return true;
importExportedNames(prefix, name);
return true;
}
-void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
+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,
+ uiob->qualifiedTypeNameId->name == QLatin1String("alias"),
+ 0);
+ property.setType(childScope);
+ m_currentScope->addProperty(property);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
{
+ using namespace QQmlJS::AST;
+
QString name {};
auto id = uiod->qualifiedTypeNameId;
QStringRef prefix = uiod->qualifiedTypeNameId->name;
@@ -802,8 +829,7 @@ 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
- 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 {};
+ 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);
- auto metaObject = m_exportedName2MetaObject[scope->name()];
+ targetScope = m_exportedName2Scope.value(scope->name()).get();
} 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;
}
@@ -862,5 +889,49 @@ 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 *)
+{
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
+{
+ using namespace QQmlJS::AST;
+ ExpressionNode *base = fieldMember->base;
+ while (auto *nested = cast<NestedExpression *>(base))
+ base = nested->expression;
+
+ if (m_fieldMemberBase == base) {
+ QString type;
+ if (auto *binary = cast<BinaryExpression *>(base)) {
+ if (binary->op == QSOperator::As) {
+ if (auto *right = cast<IdentifierExpression *>(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 80413bd402..6668b53b08 100644
--- a/tools/qmllint/findunqualified.h
+++ b/tools/qmllint/findunqualified.h
@@ -29,32 +29,49 @@
#ifndef FINDUNQUALIFIED_H
#define FINDUNQUALIFIED_H
-#include "qmljstypedescriptionreader.h"
-#include "qcoloroutput_p.h"
-
-#include <private/qqmljsastvisitor_p.h>
-#include <private/qqmljsast_p.h>
-
-#include <QScopedPointer>
-
-class ScopeTree;
-enum class ScopeType;
-
-class FindUnqualifiedIDVisitor : public QQmlJS::AST::Visitor {
-
+//
+// 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 <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+
+#include <QtCore/qscopedpointer.h>
+
+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:
+ struct Import {
+ QHash<QString, ScopeTree::ConstPtr> objects;
+ QList<ModuleApiInfo> moduleApis;
+ QStringList dependencies;
+ };
+
QScopedPointer<ScopeTree> m_rootScope;
ScopeTree *m_currentScope;
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> m_exportedName2MetaObject;
+ QQmlJS::AST::ExpressionNode *m_fieldMemberBase = nullptr;
+ QHash<QString, ScopeTree::ConstPtr> m_types;
+ QHash<QString, ScopeTree::ConstPtr> m_exportedName2Scope;
QStringList m_qmltypeDirs;
- const QString& m_code;
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> m_qmlid2meta;
+ QString m_code;
+ QHash<QString, const ScopeTree *> m_qmlid2scope;
QString m_rootId;
QString m_filePath;
QSet<QPair<QString, QString>> m_alreadySeenImports;
@@ -62,17 +79,31 @@ 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<OutstandingConnection, 3> 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(QString id, QString prefix, int major, int minor);
- LanguageUtils::FakeMetaObject* localQmlFile2FakeMetaObject(QString filePath);
+ void importHelper(const QString &module, const QString &prefix = QString(),
+ int major = -1, int minor = -1);
- void importDirectory(const QString &directory, const QString &prefix);
- void importExportedNames(QStringRef prefix, QString name);
+ void readQmltypes(const QString &filename, Import &result);
+ Import readQmldir(const QString &dirname);
+ void processImport(const QString &prefix, const Import &import);
+
+ ScopeTree *localFile2ScopeTree(const QString &filePath);
+
+ void importFileOrDirectory(const QString &directory, const QString &prefix);
+ void importExportedNames(const QStringRef &prefix, QString name);
+
+ void parseHeaders(QQmlJS::AST::UiHeaderItemList *headers);
+ ScopeTree *parseProgram(QQmlJS::AST::Program *program, const QString &name);
void throwRecursionDepthError() override;
@@ -128,7 +159,11 @@ 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;
+ bool visit(QQmlJS::AST::BinaryExpression *) override;
+ void endVisit(QQmlJS::AST::BinaryExpression *) override;
+};
#endif // FINDUNQUALIFIED_H
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/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 <QDebug>
-#include <QFile>
-#include <QFileInfo>
+#include "findunqualified.h"
+
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qcoreapplication.h>
+
#if QT_CONFIG(commandlineparser)
-#include <QCommandLineParser>
+#include <QtCore/qcommandlineparser.h>
#endif
-#include <QCoreApplication>
#ifndef QT_BOOTSTRAPPED
-#include <QLibraryInfo>
+#include <QtCore/qlibraryinfo.h>
#endif
-#include <private/qqmljslexer_p.h>
-#include <private/qqmljsparser_p.h>
-#include <private/qqmljsengine_p.h>
-#include <private/qqmljsastvisitor_p.h>
-#include <private/qqmljsast_p.h>
-
-#include "findunqualified.h"
-
-static bool lint_file(const QString &filename, 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..d67de2edcd
--- /dev/null
+++ b/tools/qmllint/metatypes.h
@@ -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$
+**
+****************************************************************************/
+
+#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 <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+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 ScopeTree;
+class MetaProperty
+{
+ QString m_propertyName;
+ QString m_typeName;
+ const ScopeTree *m_type = nullptr;
+ 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, 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)
+ {}
+
+ 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; }
+ bool isPointer() const { return m_isPointer; }
+ bool isAlias() const { return m_isAlias; }
+ 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 <QFile>
-#include <QHash>
-#include <QTextCodec>
+#include "qcoloroutput.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qtextcodec.h>
#ifndef Q_OS_WIN
#include <unistd.h>
#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
@@ -215,40 +218,17 @@ const char *const ColorOutputPrivate::backgrounds[] =
*/
/*!
- 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_p.h b/tools/qmllint/qcoloroutput.h
index aefa765a87..92f4b47ff0 100644
--- a/tools/qmllint/qcoloroutput_p.h
+++ b/tools/qmllint/qcoloroutput.h
@@ -26,6 +26,9 @@
**
****************************************************************************/
+#ifndef QCOLOROUTPUT_H
+#define QCOLOROUTPUT_H
+
//
// W A R N I N G
// -------------
@@ -36,12 +39,8 @@
//
// We mean it.
-#ifndef QCOLOROUTPUT_P_H
-#define QCOLOROUTPUT_P_H
-
-#include <QtCore/QtGlobal>
-#include <QtCore/QHash>
-#include <QScopedPointer>
+#include <QtCore/qglobal.h>
+#include <QtCore/qscopedpointer.h>
class ColorOutputPrivate;
@@ -86,15 +85,13 @@ public:
DefaultColor = 1 << SpecialShift
};
- typedef QFlags<ColorCodeComponent> ColorCode;
- typedef QHash<int, ColorCode> ColorMapping;
+ using ColorCode = QFlags<ColorCodeComponent>;
+ using ColorMapping = QHash<int, ColorCode>;
ColorOutput(bool silent);
~ColorOutput();
- void setColorMapping(const ColorMapping &cMapping);
- ColorMapping colorMapping() const;
- void insertColorMapping(int colorID, const ColorCode colorCode);
+ void insertMapping(int colorID, ColorCode colorCode);
void writeUncolored(const QString &message);
void write(const QString &message, int color = -1);
@@ -102,9 +99,9 @@ public:
private:
QScopedPointer<ColorOutputPrivate> d;
- Q_DISABLE_COPY(ColorOutput)
+ Q_DISABLE_COPY_MOVE(ColorOutput)
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ColorOutput::ColorCode)
-#endif
+#endif // QCOLOROUTPUT_H
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 <private/qqmljsastfwd_p.h>
-#include "fakemetaobject.h"
-
-// for Q_DECLARE_TR_FUNCTIONS
-#include <QCoreApplication>
-
-QT_BEGIN_NAMESPACE
-class QIODevice;
-class QBuffer;
-
-namespace QQmlJS {
-
-class ModuleApiInfo
-{
-public:
- QString uri;
- LanguageUtils::ComponentVersion version;
- QString cppName;
-};
-
-
-class TypeDescriptionReader
-{
- Q_DECLARE_TR_FUNCTIONS(QQmlJS::TypeDescriptionReader)
-
-public:
- explicit TypeDescriptionReader(const QString &fileName, const QString &data);
- ~TypeDescriptionReader();
-
- bool operator()(
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> *objects,
- QList<ModuleApiInfo> *moduleApis,
- QStringList *dependencies);
- QString errorMessage() const;
- QString warningMessage() const;
-
-private:
- void readDocument(AST::UiProgram *ast);
- void readModule(AST::UiObjectDefinition *ast);
- void readDependencies(AST::UiScriptBinding *ast);
- void readComponent(AST::UiObjectDefinition *ast);
- void readModuleApi(AST::UiObjectDefinition *ast);
- void readSignalOrMethod(AST::UiObjectDefinition *ast, bool isMethod, LanguageUtils::FakeMetaObject::Ptr fmo);
- void readProperty(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
- void readEnum(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
- void readParameter(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaMethod *fmm);
-
- QString readStringBinding(AST::UiScriptBinding *ast);
- bool readBoolBinding(AST::UiScriptBinding *ast);
- double readNumericBinding(AST::UiScriptBinding *ast);
- LanguageUtils::ComponentVersion readNumericVersionBinding(AST::UiScriptBinding *ast);
- int readIntBinding(AST::UiScriptBinding *ast);
- void readExports(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
- void readMetaObjectRevisions(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaObject::Ptr fmo);
- void readEnumValues(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaEnum *fme);
-
- void addError(const AST::SourceLocation &loc, const QString &message);
- void addWarning(const AST::SourceLocation &loc, const QString &message);
-
- QString _fileName;
- QString _source;
- QString _errorMessage;
- QString _warningMessage;
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> *_objects;
- QList<ModuleApiInfo> *_moduleApis = nullptr;
- QStringList *_dependencies = nullptr;
-};
-
-} // namespace QQmlJS
-QT_END_NAMESPACE
-
-#endif // QMLJSTYPEDESCRIPTIONREADER_H
diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro
index 76363a7cd8..4b7ca947cf 100644
--- a/tools/qmllint/qmllint.pro
+++ b/tools/qmllint/qmllint.pro
@@ -1,14 +1,14 @@
option(host_build)
-QT = core qmldevtools-private
+QT = core-private qmldevtools-private
SOURCES += main.cpp \
componentversion.cpp \
- fakemetaobject.cpp \
findunqualified.cpp \
- qmljstypedescriptionreader.cpp \
+ importedmembersvisitor.cpp \
qcoloroutput.cpp \
- scopetree.cpp
+ scopetree.cpp \
+ typedescriptionreader.cpp
QMAKE_TARGET_DESCRIPTION = QML Syntax Verifier
@@ -16,8 +16,9 @@ load(qt_tool)
HEADERS += \
componentversion.h \
- fakemetaobject.h \
findunqualified.h \
- qmljstypedescriptionreader.h \
- qcoloroutput_p.h \
- scopetree.h
+ importedmembersvisitor.h \
+ metatypes.h \
+ qcoloroutput.h \
+ scopetree.h \
+ typedescriptionreader.h
diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp
index 1e873cca8f..2ca3ed9a67 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 <QtCore/qqueue.h>
#include <algorithm>
-#include <QQueue>
-
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::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name)
+{
+ Q_ASSERT(type != ScopeType::QMLScope
+ || !m_parentScope
+ || m_parentScope->m_scopeType == ScopeType::QMLScope
+ || m_parentScope->m_name == "global");
+ auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this});
m_childScopes.push_back(childScope);
return childScope;
}
-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.propertyName() + QLatin1String("Changed"), "void");
+ addMethod(method);
}
void ScopeTree::addUnmatchedSignalHandler(const QString &handler,
@@ -92,52 +87,191 @@ bool ScopeTree::isIdInCurrentScope(const QString &id) const
return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id);
}
-void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair<QString, QQmlJS::AST::SourceLocation> &id_loc_pair, const QSet<QString>& 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);
- }
+void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location) {
+ m_currentFieldMember = new FieldMemberList {id, QString(), location, {}};
+ m_accessedIdentifiers.push_back(std::unique_ptr<FieldMemberList>(m_currentFieldMember));
}
-bool ScopeTree::isVisualRootScope() const
+void ScopeTree::accessMember(const QString &name, const QString &parentType,
+ const QQmlJS::AST::SourceLocation &location)
+{
+ Q_ASSERT(m_currentFieldMember);
+ auto *fieldMember = new FieldMemberList {name, parentType, location, {}};
+ m_currentFieldMember->m_child.reset(fieldMember);
+ m_currentFieldMember = fieldMember;
+}
+
+void ScopeTree::resetMemberScope()
{
- return m_parentScope && m_parentScope->m_parentScope && m_parentScope->m_parentScope->m_parentScope == nullptr;
+ m_currentFieldMember = nullptr;
}
-QString ScopeTree::name() const
+bool ScopeTree::isVisualRootScope() const
{
- return m_name;
+ return m_parentScope && m_parentScope->m_parentScope
+ && m_parentScope->m_parentScope->m_parentScope == nullptr;
}
-struct IssueLocationWithContext
+class 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() 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::checkMemberAccess(
+ const QString &code,
+ FieldMemberList *members,
+ const ScopeTree *scope,
+ const QHash<QString, ScopeTree::ConstPtr> &types,
+ ColorOutput& colorOut) const
+{
+ if (!members->m_child)
+ return true;
+
+ Q_ASSERT(scope != nullptr);
+
+ const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name();
+ const auto &access = members->m_child;
+
+ const auto scopeIt = scope->m_properties.find(access->m_name);
+ if (scopeIt != scope->m_properties.end()) {
+ const QString typeName = access->m_parentType.isEmpty() ? scopeIt->typeName()
+ : access->m_parentType;
+ if (scopeIt->isList() || typeName == QLatin1String("string")) {
+ if (access->m_child && access->m_child->m_name != QLatin1String("length")) {
+ colorOut.write("Warning: ", Warning);
+ colorOut.write(
+ QString::fromLatin1(
+ "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n")
+ .arg(access->m_name)
+ .arg(QLatin1String(scopeIt->isList() ? "list" : "string"))
+ .arg(access->m_child->m_name)
+ .arg(access->m_child->m_location.startLine)
+ .arg(access->m_child->m_location.startColumn), Normal);
+ printContext(colorOut, code, access->m_child->m_location);
+ return false;
+ }
+ return true;
+ }
+ const ScopeTree *type = (scopeIt->type() && access->m_parentType.isEmpty())
+ ? scopeIt->type()
+ : types.value(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 ScopeTree *propType = access->m_parentType.isEmpty()
+ ? typeIt->type()
+ : types.value(access->m_parentType).get();
+ return checkMemberAccess(code, access.get(),
+ propType ? propType : types.value(typeIt->typeName()).get(),
+ types, colorOut);
+ }
+
+ const auto typeMethodIt = type->m_methods.find(access->m_name);
+ if (typeMethodIt != type->m_methods.end()) {
+ if (access->m_child == nullptr)
+ return true;
+
+ colorOut.write("Warning: ", Warning);
+ colorOut.write(QString::fromLatin1(
+ "\"%1\" is a method. You cannot access \"%2\" on it at %3:%4\n")
+ .arg(access->m_name)
+ .arg(access->m_child->m_name)
+ .arg(access->m_child->m_location.startLine)
+ .arg(access->m_child->m_location.startColumn), Normal);
+ printContext(colorOut, code, access->m_child->m_location);
+ return false;
+ }
+
+ type = types.value(type->superclassName());
+ }
+
+ if (access->m_name.front().isUpper() && scope->scopeType() == ScopeType::QMLScope) {
+ // may be an attached type
+ const auto it = types.find(access->m_name);
+ if (it != types.end() && !(*it)->attachedTypeName().isEmpty()) {
+ const auto attached = types.find((*it)->attachedTypeName());
+ if (attached != types.end())
+ return checkMemberAccess(code, access.get(), attached->get(), types, colorOut);
+ }
}
- QStringRef beforeText;
- QStringRef issueText;
- QStringRef afterText;
+ 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<QString, LanguageUtils::FakeMetaObject::ConstPtr> &qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput& colorOut) const
+bool ScopeTree::recheckIdentifiers(
+ const QString &code,
+ const QHash<QString, const ScopeTree *> &qmlIDs,
+ const QHash<QString, ScopeTree::ConstPtr> &types,
+ const ScopeTree *root, const QString &rootId,
+ ColorOutput& colorOut) const
{
bool noUnqualifiedIdentifier = true;
// revisit all scopes
- QQueue<const ScopeTree*> workQueue;
+ QQueue<const ScopeTree *> 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,120 +281,161 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, Lan
printContext(colorOut, code, handler.second);
}
- for (auto idLocationPair : 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;
- colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine,
- location.startColumn), Normal);
+ auto location = memberAccessTree->m_location;
+ colorOut.write(QString::fromLatin1("unqualified access at %1:%2\n")
+ .arg(location.startLine).arg(location.startColumn),
+ Normal);
printContext(colorOut, code, location);
// root(JS) --> program(qml) --> (first element)
- 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(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("<id>")) {
colorOut.write("Note: ", Warning);
colorOut.write(("You first have to give the root element an id\n"));
}
IssueLocationWithContext issueLocationWithContext {code, location};
- colorOut.write(issueLocationWithContext.beforeText.toString(), Normal);
+ colorOut.write(issueLocationWithContext.beforeText().toString(), Normal);
colorOut.write(rootId + QLatin1Char('.'), Hint);
- colorOut.write(issueLocationWithContext.issueText.toString(), Normal);
- colorOut.write(issueLocationWithContext.afterText + QLatin1Char('\n'), Normal);
- } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) {
- auto qmlScope = currentScope->getCurrentQMLScope();
- auto methodUsages = qmlScope->m_injectedSignalIdentifiers.values(idLocationPair.first);
- auto location = idLocationPair.second;
+ colorOut.write(issueLocationWithContext.issueText().toString(), Normal);
+ colorOut.write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal);
+ } else if (currentScope->isIdInjectedFromSignal(memberAccessTree->m_name)) {
+ auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers
+ .values(memberAccessTree->m_name);
+ auto location = memberAccessTree->m_location;
// sort the list of signal handlers by their occurrence in the source code
// then, we select the first one whose location is after the unqualified id
// and go one step backwards to get the one which we actually need
- std::sort(methodUsages.begin(), methodUsages.end(), [](const MethodUsage m1, const MethodUsage m2) {
- return m1.loc.startLine < m2.loc.startLine || (m1.loc.startLine == m2.loc.startLine && m1.loc.startColumn < m2.loc.startColumn);
+ 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(
+ memberAccessTree->m_name + QString::fromLatin1(
+ " is accessible in this scope because "
+ "you are handling a signal at %1:%2\n")
+ .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn),
+ Normal);
colorOut.write("Consider using a function instead\n", Normal);
IssueLocationWithContext context {code, methodUsage.loc};
- colorOut.write(context.beforeText + QLatin1Char(' '));
+ colorOut.write(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) {
- workQueue.enqueue(childScope);
- }
+ for (auto const &childScope: currentScope->m_childScopes)
+ workQueue.enqueue(childScope.get());
}
return noUnqualifiedIdentifier;
}
-QMap<QString, LanguageUtils::FakeMetaMethod>const &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
+bool ScopeTree::isIdInjectedFromSignal(const QString &id) const
{
- auto qmlScope = getCurrentQMLScope();
- return qmlScope->m_injectedSignalIdentifiers.contains(id);
+ return currentQMLScope()->m_injectedSignalIdentifiers.contains(id);
}
-const ScopeTree *ScopeTree::getCurrentQMLScope() const
+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;
-}
-
-ScopeTree *ScopeTree::getCurrentQMLScope()
-{
- auto qmlScope = this;
- while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) {
- qmlScope = qmlScope->m_parentScope;
- }
return qmlScope;
}
@@ -268,30 +443,46 @@ 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)
+void ScopeTree::updateParentProperty(const ScopeTree *scope)
{
- if (metaObject) {
- auto methodCount = metaObject->methodCount();
- for (auto i = 0; i < methodCount; ++i) {
- auto method = metaObject->method(i);
- this->addMethod(method);
- }
- }
+ auto it = m_properties.find(QLatin1String("parent"));
+ if (it != m_properties.end()
+ && scope->name() != QLatin1String("Component")
+ && scope->name() != QLatin1String("program"))
+ it->setType(scope);
+}
+
+ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version,
+ int metaObjectRevision) :
+ m_package(std::move(package)),
+ m_type(std::move(type)),
+ m_version(version),
+ m_metaObjectRevision(metaObjectRevision)
+{
+}
+
+bool ScopeTree::Export::isValid() const
+{
+ return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty();
}
diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h
index 52cdc45e96..f5d1155a49 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 <QSet>
-#include <QString>
-#include <QMap>
+#include "metatypes.h"
+#include "componentversion.h"
-enum MessageColors{
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljssourcelocation_p.h>
+
+#include <QtCore/qset.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
+
+enum MessageColors
+{
Error,
Warning,
Info,
@@ -54,58 +67,157 @@ 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="<none given>", ScopeTree* parentScope=nullptr);
- ~ScopeTree() {qDeleteAll(m_childScopes);}
+ using Ptr = QSharedPointer<ScopeTree>;
+ using ConstPtr = QSharedPointer<const ScopeTree>;
+
+ 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 = QString(),
+ ScopeTree *parentScope = nullptr);
+
+ 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,
+ 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<QString, QQmlJS::AST::SourceLocation> const& id_loc_pair, const QSet<QString>& unknownImports);
+ bool isIdInCurrentScope(const QString &id) const;
+ void addIdToAccessed(const QString &id, const QQmlJS::AST::SourceLocation &location);
+ void accessMember(const QString &name, const QString &parentType,
+ const QQmlJS::AST::SourceLocation &location);
+ void resetMemberScope();
bool isVisualRootScope() const;
- QString name() const;
+ QString name() const { return m_name; }
+
+ bool recheckIdentifiers(
+ const QString &code,
+ const QHash<QString, const ScopeTree *> &qmlIDs,
+ const QHash<QString, ScopeTree::ConstPtr> &types,
+ const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const;
+
+ ScopeType scopeType() const { return m_scopeType; }
+
+ void addMethods(const QHash<QString, MetaMethod> &methods) { m_methods.insert(methods); }
+ void addMethod(const MetaMethod &method) { m_methods.insert(method.methodName(), method); }
+ QHash<QString, MetaMethod> methods() const { return m_methods; }
+
+ void addEnum(const MetaEnum &fakeEnum) { m_enums.insert(fakeEnum.name(), fakeEnum); }
+ QHash<QString, MetaEnum> enums() const { return m_enums; }
+
+ QString className() const { return m_className; }
+ void setClassName(const QString &name) { m_className = name; }
- bool recheckIdentifiers(const QString &code, const QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr>& qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const;
- ScopeType scopeType();
- void addMethod(LanguageUtils::FakeMetaMethod);
- void addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject);
- QMap<QString, LanguageUtils::FakeMetaMethod>const & methods() const;
+ void addExport(const QString &name, const QString &package, const ComponentVersion &version);
+ void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision);
+ QList<Export> 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.propertyName(), prop); }
+ QHash<QString, MetaProperty> properties() const { return m_properties; }
+ void updateParentProperty(const ScopeTree *scope);
+
+ 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<QString> m_currentScopeJSIdentifiers;
- QSet<QString> m_currentScopeQMLIdentifiers;
+ struct FieldMemberList
+ {
+ QString m_name;
+ QString m_parentType;
+ QQmlJS::AST::SourceLocation m_location;
+ std::unique_ptr<FieldMemberList> m_child;
+ };
+
+ QSet<QString> m_jsIdentifiers;
QMultiHash<QString, MethodUsage> m_injectedSignalIdentifiers;
- QMap<QString, LanguageUtils::FakeMetaMethod> m_methods;
- QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_accessedIdentifiers;
- QVector<ScopeTree*> m_childScopes;
+
+ QHash<QString, MetaMethod> m_methods;
+ QHash<QString, MetaProperty> m_properties;
+ QHash<QString, MetaEnum> m_enums;
+
+ std::vector<std::unique_ptr<FieldMemberList>> m_accessedIdentifiers;
+ FieldMemberList *m_currentFieldMember = nullptr;
+
+ QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_unmatchedSignalHandlers;
+
+ QVector<ScopeTree::Ptr> m_childScopes;
ScopeTree *m_parentScope;
+
QString m_name;
- ScopeType m_scopeType;
- QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_unmatchedSignalHandlers;
+ QString m_className;
+ QString m_superName;
+
+ ScopeType m_scopeType = ScopeType::QMLScope;
+ QList<Export> m_exports;
- 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,
+ 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;
+ bool checkMemberAccess(
+ const QString &code,
+ FieldMemberList *members,
+ const ScopeTree *scope,
+ const QHash<QString, ScopeTree::ConstPtr> &types,
+ ColorOutput& colorOut) const;
};
+
#endif // SCOPETREE_H
diff --git a/tools/qmllint/qmljstypedescriptionreader.cpp b/tools/qmllint/typedescriptionreader.cpp
index b8aecdddb1..3dc87ffc8d 100644
--- a/tools/qmllint/qmljstypedescriptionreader.cpp
+++ b/tools/qmllint/typedescriptionreader.cpp
@@ -26,25 +26,18 @@
**
****************************************************************************/
-#include "qmljstypedescriptionreader.h"
+#include "typedescriptionreader.h"
-#include <private/qqmljsparser_p.h>
-#include <private/qqmljslexer_p.h>
-#include <private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
-#include <QDir>
-
-#define QTC_ASSERT_STRINGIFY_HELPER(x) #x
-#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x)
-#define QTC_ASSERT_STRING(cond) qDebug() << (\
- "\"" cond"\" in file " __FILE__ ", line " QTC_ASSERT_STRINGIFY(__LINE__))
-#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0)
+#include <QtCore/qdir.h>
using namespace QQmlJS;
using namespace QQmlJS::AST;
-using namespace LanguageUtils;
-QString toString(const AST::UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
+QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
{
QString result;
@@ -58,17 +51,8 @@ QString toString(const AST::UiQualifiedId *qualifiedId, QChar delimiter = QLatin
return result;
}
-TypeDescriptionReader::TypeDescriptionReader(const QString &fileName, const QString &data)
- : _fileName (fileName), _source(data), _objects(0)
-{
-}
-
-TypeDescriptionReader::~TypeDescriptionReader()
-{
-}
-
bool TypeDescriptionReader::operator()(
- QHash<QString, FakeMetaObject::ConstPtr> *objects,
+ QHash<QString, ScopeTree::ConstPtr> *objects,
QList<ModuleApiInfo> *moduleApis,
QStringList *dependencies)
{
@@ -77,32 +61,22 @@ bool TypeDescriptionReader::operator()(
Lexer lexer(&engine);
Parser parser(&engine);
- lexer.setCode(_source, /*line = */ 1, /*qmlMode = */true);
+ lexer.setCode(m_source, /*lineno = */ 1, /*qmlMode = */true);
if (!parser.parse()) {
- _errorMessage = QString::fromLatin1("%1:%2: %3").arg(
+ m_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;
+ m_objects = objects;
+ m_moduleApis = moduleApis;
+ m_dependencies = dependencies;
readDocument(parser.ast());
- return _errorMessage.isEmpty();
-}
-
-QString TypeDescriptionReader::errorMessage() const
-{
- return _errorMessage;
-}
-
-QString TypeDescriptionReader::warningMessage() const
-{
- return _warningMessage;
+ return m_errorMessage.isEmpty();
}
void TypeDescriptionReader::readDocument(UiProgram *ast)
@@ -112,12 +86,12 @@ void TypeDescriptionReader::readDocument(UiProgram *ast)
return;
}
- if (!ast->headers || ast->headers->next || !AST::cast<AST::UiImport *>(ast->headers->headerItem)) {
+ if (!ast->headers || ast->headers->next || !cast<UiImport *>(ast->headers->headerItem)) {
addError(SourceLocation(), tr("Expected a single import."));
return;
}
- UiImport *import = AST::cast<AST::UiImport *>(ast->headers->headerItem);
+ auto *import = cast<UiImport *>(ast->headers->headerItem);
if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) {
addError(import->importToken, tr("Expected import of QtQuick.tooling."));
return;
@@ -129,7 +103,8 @@ void TypeDescriptionReader::readDocument(UiProgram *ast)
}
if (import->version->majorVersion != 1) {
- addError(import->version->firstSourceLocation(), tr("Major version different from 1 not supported."));
+ addError(import->version->firstSourceLocation(),
+ tr("Major version different from 1 not supported."));
return;
}
@@ -138,7 +113,7 @@ void TypeDescriptionReader::readDocument(UiProgram *ast)
return;
}
- UiObjectDefinition *module = AST::cast<UiObjectDefinition *>(ast->members->member);
+ auto *module = cast<UiObjectDefinition *>(ast->members->member);
if (!module) {
addError(SourceLocation(), tr("Expected document to contain a single object definition."));
return;
@@ -156,9 +131,9 @@ void TypeDescriptionReader::readModule(UiObjectDefinition *ast)
{
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
- UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member);
+ auto *component = cast<UiObjectDefinition *>(member);
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) {
readDependencies(script);
continue;
@@ -168,7 +143,8 @@ void TypeDescriptionReader::readModule(UiObjectDefinition *ast)
if (component)
typeName = toString(component->qualifiedTypeNameId);
- if (!component || (typeName != QLatin1String("Component") && typeName != QLatin1String("ModuleApi"))) {
+ if (!component || (typeName != QLatin1String("Component")
+ && typeName != QLatin1String("ModuleApi"))) {
continue;
}
@@ -181,8 +157,8 @@ void TypeDescriptionReader::readModule(UiObjectDefinition *ast)
void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &message)
{
- _errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
- QDir::toNativeSeparators(_fileName),
+ m_errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
+ QDir::toNativeSeparators(m_fileName),
QString::number(loc.startLine),
QString::number(loc.startColumn),
message);
@@ -190,8 +166,8 @@ void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &m
void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message)
{
- _warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
- QDir::toNativeSeparators(_fileName),
+ m_warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
+ QDir::toNativeSeparators(m_fileName),
QString::number(loc.startLine),
QString::number(loc.startColumn),
message);
@@ -199,83 +175,82 @@ void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString
void TypeDescriptionReader::readDependencies(UiScriptBinding *ast)
{
- ExpressionStatement *stmt = AST::cast<ExpressionStatement*>(ast->statement);
+ auto *stmt = cast<ExpressionStatement*>(ast->statement);
if (!stmt) {
addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions"));
return;
}
- ArrayPattern *exp = AST::cast<ArrayPattern *>(stmt->expression);
+ auto *exp = cast<ArrayPattern *>(stmt->expression);
if (!exp) {
addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions"));
return;
}
for (PatternElementList *l = exp->elements; l; l = l->next) {
- //StringLiteral *str = AST::cast<StringLiteral *>(l->element->initializer);
- StringLiteral *str = AST::cast<StringLiteral *>(l->element->initializer);
- *_dependencies << str->value.toString();
+ auto *str = cast<StringLiteral *>(l->element->initializer);
+ *m_dependencies << str->value.toString();
}
}
void TypeDescriptionReader::readComponent(UiObjectDefinition *ast)
{
- FakeMetaObject::Ptr fmo(new FakeMetaObject);
+ ScopeTree::Ptr scope(new ScopeTree(ScopeType::QMLScope));
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
- UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member);
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *component = cast<UiObjectDefinition *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (component) {
QString name = toString(component->qualifiedTypeNameId);
if (name == QLatin1String("Property"))
- readProperty(component, fmo);
+ readProperty(component, scope);
else if (name == QLatin1String("Method") || name == QLatin1String("Signal"))
- readSignalOrMethod(component, name == QLatin1String("Method"), fmo);
+ readSignalOrMethod(component, name == QLatin1String("Method"), scope);
else if (name == QLatin1String("Enum"))
- readEnum(component, fmo);
+ readEnum(component, scope);
else
addWarning(component->firstSourceLocation(),
- tr("Expected only Property, Method, Signal and Enum object definitions, not \"%1\".")
- .arg(name));
+ 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));
+ scope->setClassName(readStringBinding(script));
} else if (name == QLatin1String("prototype")) {
- fmo->setSuperclassName(readStringBinding(script));
+ scope->setSuperclassName(readStringBinding(script));
} else if (name == QLatin1String("defaultProperty")) {
- fmo->setDefaultPropertyName(readStringBinding(script));
+ scope->setDefaultPropertyName(readStringBinding(script));
} else if (name == QLatin1String("exports")) {
- readExports(script, fmo);
+ readExports(script, scope);
} else if (name == QLatin1String("exportMetaObjectRevisions")) {
- readMetaObjectRevisions(script, fmo);
+ readMetaObjectRevisions(script, scope);
} else if (name == QLatin1String("attachedType")) {
- fmo->setAttachedTypeName(readStringBinding(script));
+ scope->setAttachedTypeName(readStringBinding(script));
} else if (name == QLatin1String("isSingleton")) {
- fmo->setIsSingleton(readBoolBinding(script));
+ scope->setIsSingleton(readBoolBinding(script));
} else if (name == QLatin1String("isCreatable")) {
- fmo->setIsCreatable(readBoolBinding(script));
+ scope->setIsCreatable(readBoolBinding(script));
} else if (name == QLatin1String("isComposite")) {
- fmo->setIsComposite(readBoolBinding(script));
+ 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));
+ 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."));
+ addWarning(member->firstSourceLocation(),
+ tr("Expected only script bindings and object definitions."));
}
}
- if (fmo->className().isEmpty()) {
+ 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
- fmo->addExport(fmo->className(), QStringLiteral("<cpp>"), ComponentVersion());
- fmo->updateFingerprint();
- _objects->insert(fmo->className(), fmo);
+ scope->addExport(scope->className(), QStringLiteral("<cpp>"), ComponentVersion());
+ m_objects->insert(scope->className(), scope);
}
void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast)
@@ -284,7 +259,7 @@ void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast)
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (script) {
const QString name = toString(script->qualifiedId);
@@ -304,58 +279,65 @@ void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast)
}
if (!apiInfo.version.isValid()) {
- addError(ast->firstSourceLocation(), tr("ModuleApi definition has no or invalid version binding."));
+ addError(ast->firstSourceLocation(),
+ tr("ModuleApi definition has no or invalid version binding."));
return;
}
- if (_moduleApis)
- _moduleApis->append(apiInfo);
+ if (m_moduleApis)
+ m_moduleApis->append(apiInfo);
}
-void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, FakeMetaObject::Ptr fmo)
+void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod,
+ const ScopeTree::Ptr &scope)
{
- FakeMetaMethod fmm;
+ MetaMethod metaMethod;
// ### confusion between Method and Slot. Method should be removed.
if (isMethod)
- fmm.setMethodType(FakeMetaMethod::Slot);
+ metaMethod.setMethodType(MetaMethod::Slot);
else
- fmm.setMethodType(FakeMetaMethod::Signal);
+ metaMethod.setMethodType(MetaMethod::Signal);
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
- UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member);
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *component = cast<UiObjectDefinition *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (component) {
QString name = toString(component->qualifiedTypeNameId);
- if (name == QLatin1String("Parameter"))
- readParameter(component, &fmm);
- else
- addWarning(component->firstSourceLocation(), tr("Expected only Parameter object definitions."));
+ 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"))
- 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."));
-
+ 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."));
+ 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."));
+ if (metaMethod.methodName().isEmpty()) {
+ addError(ast->firstSourceLocation(),
+ tr("Method or signal is missing a name script binding."));
return;
}
- fmo->addMethod(fmm);
+ scope->addMethod(metaMethod);
}
-void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, FakeMetaObject::Ptr fmo)
+void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTree::Ptr &scope)
{
QString name;
QString type;
@@ -366,69 +348,75 @@ void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, FakeMetaObject
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (!script) {
addWarning(member->firstSourceLocation(), tr("Expected script binding."));
continue;
}
QString id = toString(script->qualifiedId);
- if (id == QLatin1String("name"))
+ if (id == QLatin1String("name")) {
name = readStringBinding(script);
- else if (id == QLatin1String("type"))
+ } else if (id == QLatin1String("type")) {
type = readStringBinding(script);
- else if (id == QLatin1String("isPointer"))
+ } else if (id == QLatin1String("isPointer")) {
isPointer = readBoolBinding(script);
- else if (id == QLatin1String("isReadonly"))
+ } else if (id == QLatin1String("isReadonly")) {
isReadonly = readBoolBinding(script);
- else if (id == QLatin1String("isList"))
+ } else if (id == QLatin1String("isList")) {
isList = readBoolBinding(script);
- else if (id == QLatin1String("revision"))
+ } else if (id == QLatin1String("revision")) {
revision = readIntBinding(script);
- else
- addWarning(script->firstSourceLocation(), tr("Expected only type, name, revision, isPointer, isReadonly and isList script bindings."));
+ } 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."));
+ addError(ast->firstSourceLocation(),
+ tr("Property object is missing a name or type script binding."));
return;
}
- fmo->addProperty(FakeMetaProperty(name, type, isList, !isReadonly, isPointer, revision));
+ scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, false, revision));
}
-void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, FakeMetaObject::Ptr fmo)
+void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope)
{
- FakeMetaEnum fme;
+ MetaEnum metaEnum;
for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
UiObjectMember *member = it->member;
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (!script) {
addWarning(member->firstSourceLocation(), tr("Expected script binding."));
continue;
}
QString name = toString(script->qualifiedId);
- if (name == QLatin1String("name"))
- fme.setName(readStringBinding(script));
- else if (name == QLatin1String("values"))
- readEnumValues(script, &fme);
- else
- addWarning(script->firstSourceLocation(), tr("Expected only name and values script bindings."));
+ 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."));
+ }
}
- fmo->addEnum(fme);
+ scope->addEnum(metaEnum);
}
-void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, FakeMetaMethod *fmm)
+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;
- UiScriptBinding *script = AST::cast<UiScriptBinding *>(member);
+ auto *script = cast<UiScriptBinding *>(member);
if (!script) {
addWarning(member->firstSourceLocation(), tr("Expected script binding."));
continue;
@@ -446,29 +434,30 @@ void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, FakeMetaMetho
} else if (id == QLatin1String("isList")) {
// ### unhandled
} else {
- addWarning(script->firstSourceLocation(), tr("Expected only name and type script bindings."));
+ addWarning(script->firstSourceLocation(),
+ tr("Expected only name and type script bindings."));
}
}
- fmm->addParameter(name, type);
+ metaMethod->addParameter(name, type);
}
QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
{
- QTC_ASSERT(ast, return QString());
+ Q_ASSERT(ast);
if (!ast->statement) {
addError(ast->colonToken, tr("Expected string after colon."));
return QString();
}
- ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
addError(ast->statement->firstSourceLocation(), tr("Expected string after colon."));
return QString();
}
- StringLiteral *stringLit = AST::cast<StringLiteral *>(expStmt->expression);
+ auto *stringLit = cast<StringLiteral *>(expStmt->expression);
if (!stringLit) {
addError(expStmt->firstSourceLocation(), tr("Expected string after colon."));
return QString();
@@ -477,23 +466,23 @@ QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
return stringLit->value.toString();
}
-bool TypeDescriptionReader::readBoolBinding(AST::UiScriptBinding *ast)
+bool TypeDescriptionReader::readBoolBinding(UiScriptBinding *ast)
{
- QTC_ASSERT(ast, return false);
+ Q_ASSERT(ast);
if (!ast->statement) {
addError(ast->colonToken, tr("Expected boolean after colon."));
return false;
}
- ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon."));
return false;
}
- TrueLiteral *trueLit = AST::cast<TrueLiteral *>(expStmt->expression);
- FalseLiteral *falseLit = AST::cast<FalseLiteral *>(expStmt->expression);
+ auto *trueLit = cast<TrueLiteral *>(expStmt->expression);
+ auto *falseLit = cast<FalseLiteral *>(expStmt->expression);
if (!trueLit && !falseLit) {
addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon."));
return false;
@@ -502,22 +491,23 @@ bool TypeDescriptionReader::readBoolBinding(AST::UiScriptBinding *ast)
return trueLit;
}
-double TypeDescriptionReader::readNumericBinding(AST::UiScriptBinding *ast)
+double TypeDescriptionReader::readNumericBinding(UiScriptBinding *ast)
{
- QTC_ASSERT(ast, return qQNaN());
+ Q_ASSERT(ast);
if (!ast->statement) {
addError(ast->colonToken, tr("Expected numeric literal after colon."));
return 0;
}
- ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
- addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon."));
+ addError(ast->statement->firstSourceLocation(),
+ tr("Expected numeric literal after colon."));
return 0;
}
- NumericLiteral *numericLit = AST::cast<NumericLiteral *>(expStmt->expression);
+ auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
if (!numericLit) {
addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
return 0;
@@ -531,26 +521,29 @@ ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBindin
ComponentVersion invalidVersion;
if (!ast || !ast->statement) {
- addError((ast ? ast->colonToken : SourceLocation()), tr("Expected numeric literal after colon."));
+ addError((ast ? ast->colonToken : SourceLocation()),
+ tr("Expected numeric literal after colon."));
return invalidVersion;
}
- ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
- addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon."));
+ addError(ast->statement->firstSourceLocation(),
+ tr("Expected numeric literal after colon."));
return invalidVersion;
}
- NumericLiteral *numericLit = AST::cast<NumericLiteral *>(expStmt->expression);
+ auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
if (!numericLit) {
addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
return invalidVersion;
}
- return ComponentVersion(_source.mid(numericLit->literalToken.begin(), numericLit->literalToken.length));
+ return ComponentVersion(m_source.mid(numericLit->literalToken.begin(),
+ numericLit->literalToken.length));
}
-int TypeDescriptionReader::readIntBinding(AST::UiScriptBinding *ast)
+int TypeDescriptionReader::readIntBinding(UiScriptBinding *ast)
{
double v = readNumericBinding(ast);
int i = static_cast<int>(v);
@@ -563,31 +556,33 @@ int TypeDescriptionReader::readIntBinding(AST::UiScriptBinding *ast)
return i;
}
-void TypeDescriptionReader::readExports(UiScriptBinding *ast, FakeMetaObject::Ptr fmo)
+void TypeDescriptionReader::readExports(UiScriptBinding *ast, const ScopeTree::Ptr &scope)
{
- QTC_ASSERT(ast, return);
+ Q_ASSERT(ast);
if (!ast->statement) {
addError(ast->colonToken, tr("Expected array of strings after colon."));
return;
}
- ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
- addError(ast->statement->firstSourceLocation(), tr("Expected array of strings after colon."));
+ addError(ast->statement->firstSourceLocation(),
+ tr("Expected array of strings after colon."));
return;
}
- ArrayPattern *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression);
+ auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
if (!arrayLit) {
addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon."));
return;
}
for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
- StringLiteral *stringLit = AST::cast<StringLiteral *>(it->element->initializer);
+ auto *stringLit = cast<StringLiteral *>(it->element->initializer);
if (!stringLit) {
- addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only string literal members."));
+ addError(arrayLit->firstSourceLocation(),
+ tr("Expected array literal with only string literal members."));
return;
}
QString exp = stringLit->value.toString();
@@ -596,7 +591,9 @@ void TypeDescriptionReader::readExports(UiScriptBinding *ast, FakeMetaObject::Pt
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'."));
+ addError(stringLit->firstSourceLocation(),
+ tr("Expected string literal to contain 'Package/Name major.minor' "
+ "or 'Name major.minor'."));
continue;
}
QString package;
@@ -605,42 +602,46 @@ void TypeDescriptionReader::readExports(UiScriptBinding *ast, FakeMetaObject::Pt
QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1));
// ### relocatable exports where package is empty?
- fmo->addExport(name, package, version);
+ scope->addExport(name, package, version);
}
}
-void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, FakeMetaObject::Ptr fmo)
+void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast,
+ const ScopeTree::Ptr &scope)
{
- QTC_ASSERT(ast, return);
+ Q_ASSERT(ast);
if (!ast->statement) {
addError(ast->colonToken, tr("Expected array of numbers after colon."));
return;
}
- ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
- addError(ast->statement->firstSourceLocation(), tr("Expected array of numbers after colon."));
+ addError(ast->statement->firstSourceLocation(),
+ tr("Expected array of numbers after colon."));
return;
}
- ArrayPattern *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression);
+ auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
if (!arrayLit) {
addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon."));
return;
}
int exportIndex = 0;
- const int exportCount = fmo->exports().size();
+ const int exportCount = scope->exports().size();
for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) {
- NumericLiteral *numberLit = cast<NumericLiteral *>(it->element->initializer);
+ auto *numberLit = cast<NumericLiteral *>(it->element->initializer);
if (!numberLit) {
- addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only number literal members."));
+ 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."));
+ addError(numberLit->firstSourceLocation(),
+ tr("Meta object revision without matching export."));
return;
}
@@ -651,11 +652,11 @@ void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, FakeMe
return;
}
- fmo->setExportMetaObjectRevision(exportIndex, metaObjectRevision);
+ scope->setExportMetaObjectRevision(exportIndex, metaObjectRevision);
}
}
-void TypeDescriptionReader::readEnumValues(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaEnum *fme)
+void TypeDescriptionReader::readEnumValues(UiScriptBinding *ast, MetaEnum *metaEnum)
{
if (!ast)
return;
@@ -664,27 +665,27 @@ void TypeDescriptionReader::readEnumValues(AST::UiScriptBinding *ast, LanguageUt
return;
}
- auto *expStmt = AST::cast<ExpressionStatement *>(ast->statement);
+ auto *expStmt = cast<ExpressionStatement *>(ast->statement);
if (!expStmt) {
addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon."));
return;
}
- if (auto *objectLit = AST::cast<ObjectPattern *>(expStmt->expression)) {
+ if (auto *objectLit = cast<ObjectPattern *>(expStmt->expression)) {
for (PatternPropertyList *it = objectLit->properties; it; it = it->next) {
if (PatternProperty *assignement = it->property) {
- if (auto *name = AST::cast<StringLiteralPropertyName *>(assignement->name)) {
- fme->addKey(name->id.toString());
+ if (auto *name = cast<StringLiteralPropertyName *>(assignement->name)) {
+ metaEnum->addKey(name->id.toString());
continue;
}
}
addError(it->firstSourceLocation(), tr("Expected strings as enum keys."));
}
- } else if (auto *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression)) {
+ } else if (auto *arrayLit = cast<ArrayPattern *>(expStmt->expression)) {
for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
if (PatternElement *element = it->element) {
- if (auto *name = AST::cast<StringLiteral *>(element->initializer)) {
- fme->addKey(name->value.toString());
+ if (auto *name = cast<StringLiteral *>(element->initializer)) {
+ metaEnum->addKey(name->value.toString());
continue;
}
}
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 <QtQml/private/qqmljsastfwd_p.h>
+
+// for Q_DECLARE_TR_FUNCTIONS
+#include <QtCore/qcoreapplication.h>
+
+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<QString, ScopeTree::ConstPtr> *objects,
+ QList<ModuleApiInfo> *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<QString, ScopeTree::ConstPtr> *m_objects = nullptr;
+ QList<ModuleApiInfo> *m_moduleApis = nullptr;
+ QStringList *m_dependencies = nullptr;
+};
+
+#endif // TYPEDESCRIPTIONREADER_H
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 <private/qqmljsengine_p.h>
-#include <private/qqmljslexer_p.h>
-#include <private/qqmljsparser_p.h>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QStringList>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QDir>
-#include <iostream>
-#include <cstdlib>
-
-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<int> _stateStack;
- QList<int> _tokens;
- QList<QString> _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<file> write output to file rather than stdout" << std::endl
- << " -v --verify-only just run the verifier, no output" << std::endl
- << " -w<width> 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/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<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine,
)
{
QSet<const QMetaObject *> metas;
- metas.insert(FriendlyQObject::qtMeta());
+ metas.insert(&Qt::staticMetaObject);
const auto qmlTypes = QQmlMetaType::qmlTypes();
for (const QQmlType &ty : qmlTypes) {
diff --git a/tools/tools.pro b/tools/tools.pro
index 25ed760903..d16f78071c 100644
--- a/tools/tools.pro
+++ b/tools/tools.pro
@@ -4,15 +4,14 @@ QT_FOR_CONFIG += qml-private
qtConfig(qml-devtools) {
SUBDIRS += \
qmllint \
- qmlmin \
- qmlimportscanner
+ qmlimportscanner \
+ qmlformat
qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen
}
qtConfig(thread):!android|android_app:!wasm:!rtems {
- SUBDIRS += \
- qml
+ qtConfig(commandlineparser): SUBDIRS += qml
qtConfig(qml-profiler): SUBDIRS += qmlprofiler
qtConfig(qml-preview): SUBDIRS += qmlpreview
@@ -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.