aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmlformat
diff options
context:
space:
mode:
authorMaximilian Goldstein <max.goldstein@qt.io>2019-12-13 16:10:46 +0100
committerMaximilian Goldstein <max.goldstein@qt.io>2019-12-19 16:45:51 +0100
commit73189151b59430671630ae931ac42c0e18cdab55 (patch)
treee57692afa5cbc0d517f8d10bcdbb792f5e1f1066 /tools/qmlformat
parent08977003850d7da27a7db8058ff860fc64eb439a (diff)
qtdeclarative/tools: Implement qmlformat
qmlformat is a tool that formats qml files according to the QML coding conventions (https://doc.qt.io/qt-5/qml-codingconventions.html). Change-Id: I359c4bd3b51f60614535f0e857d7e0b21b1d9033 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools/qmlformat')
-rw-r--r--tools/qmlformat/commentastvisitor.cpp270
-rw-r--r--tools/qmlformat/commentastvisitor.h131
-rw-r--r--tools/qmlformat/dumpastvisitor.cpp927
-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
8 files changed, 1860 insertions, 0 deletions
diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp
new file mode 100644
index 0000000000..6d2bf49ddc
--- /dev/null
+++ b/tools/qmlformat/commentastvisitor.cpp
@@ -0,0 +1,270 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "commentastvisitor.h"
+
+CommentAstVisitor::CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode) : m_engine(engine)
+{
+ rootNode->accept(this);
+
+ // Look for complete orphans that have not been attached to *any* node
+ QVector<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);
+ return true;
+}
+
+bool CommentAstVisitor::visit(UiObjectDefinition *node)
+{
+ attachComment(node);
+ return true;
+}
+
+void CommentAstVisitor::endVisit(UiObjectDefinition *node)
+{
+ m_orphanComments[node] = findOrphanComments(node);
+}
+
+bool CommentAstVisitor::visit(UiArrayBinding *node)
+{
+ attachComment(node);
+ return true;
+}
+
+void CommentAstVisitor::endVisit(UiArrayBinding *node)
+{
+ m_orphanComments[node] = findOrphanComments(node);
+}
+
+bool CommentAstVisitor::visit(UiEnumDeclaration *node)
+{
+ attachComment(node);
+ return true;
+}
+
+void CommentAstVisitor::endVisit(UiEnumDeclaration *node)
+{
+ m_orphanComments[node] = findOrphanComments(node);
+}
+
+bool CommentAstVisitor::visit(UiEnumMemberList *node)
+{
+ for (auto *item = node; item != nullptr; item = item->next) {
+ auto comment = findComment(item->memberToken,
+ item->valueToken.isValid() ? item->valueToken : item->memberToken,
+ Comment::Front | Comment::Back_Inline);
+
+ if (comment.isValid())
+ m_listItemComments[item->memberToken.begin()] = comment;
+ }
+
+ m_orphanComments[node] = findOrphanComments(node);
+
+ return true;
+}
+
+bool CommentAstVisitor::visit(UiPublicMember *node)
+{
+ attachComment(node);
+ return true;
+}
+
+bool CommentAstVisitor::visit(FunctionDeclaration *node)
+{
+ attachComment(node);
+ return true;
+}
+
+bool CommentAstVisitor::visit(UiImport *node)
+{
+ attachComment(node);
+ return true;
+}
diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h
new file mode 100644
index 0000000000..dee2bb3983
--- /dev/null
+++ b/tools/qmlformat/commentastvisitor.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef COMMENTASTVISITOR_H
+#define COMMENTASTVISITOR_H
+
+#include <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;
+
+ Comment() {}
+ 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..09bb45c2bb
--- /dev/null
+++ b/tools/qmlformat/dumpastvisitor.cpp
@@ -0,0 +1,927 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "dumpastvisitor.h"
+
+DumpAstVisitor::DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment): m_comment(comment)
+{
+ // Add all completely orphaned comments
+ m_result += getOrphanedComments(nullptr);
+
+ rootNode->accept(this);
+}
+
+static QString parseUiQualifiedId(UiQualifiedId *id)
+{
+ QString name = id->name.toString();
+ for (auto *item = id->next; item != nullptr; item = item->next) {
+ name += "." + item->name;
+ }
+
+ return name;
+}
+
+static QString operatorToString(int op)
+{
+ switch (op)
+ {
+ case QSOperator::Add: return "+";
+ case QSOperator::And: return "&&";
+ case QSOperator::InplaceAnd: return "&=";
+ case QSOperator::Assign: return "=";
+ case QSOperator::BitAnd: return "&";
+ case QSOperator::BitOr: return "|";
+ case QSOperator::BitXor: return "^";
+ case QSOperator::InplaceSub: return "-=";
+ case QSOperator::Div: return "/";
+ case QSOperator::InplaceDiv: return "/=";
+ case QSOperator::Equal: return "==";
+ case QSOperator::Exp: return "**";
+ case QSOperator::InplaceExp: return "**=";
+ case QSOperator::Ge: return ">=";
+ case QSOperator::Gt: return ">";
+ case QSOperator::In: return "in";
+ case QSOperator::InplaceAdd: return "+=";
+ case QSOperator::InstanceOf: return "instanceof";
+ case QSOperator::Le: return "<=";
+ case QSOperator::LShift: return "<<";
+ case QSOperator::InplaceLeftShift: return "<<=";
+ case QSOperator::Lt: return "<";
+ case QSOperator::Mod: return "%";
+ case QSOperator::InplaceMod: return "%=";
+ case QSOperator::Mul: return "*";
+ case QSOperator::InplaceMul: return "*=";
+ case QSOperator::NotEqual: return "!=";
+ case QSOperator::Or: return "||";
+ case QSOperator::InplaceOr: return "|=";
+ case QSOperator::RShift: return ">>";
+ case QSOperator::InplaceRightShift: return ">>=";
+ case QSOperator::StrictEqual: return "===";
+ case QSOperator::StrictNotEqual: return "!==";
+ case QSOperator::Sub: return "-";
+ case QSOperator::URShift: return ">>>";
+ case QSOperator::InplaceURightShift: return ">>>=";
+ case QSOperator::InplaceXor: return "^=";
+ case QSOperator::As: return "as";
+ case QSOperator::Coalesce: return "??";
+ case QSOperator::Invalid:
+ default:
+ return "INVALID";
+ }
+}
+
+QString DumpAstVisitor::formatComment(const Comment &comment) const
+{
+ QString result;
+
+ bool useMultilineComment = comment.isMultiline() && !comment.isSyntheticMultiline();
+
+ if (useMultilineComment)
+ result += "/*";
+ else
+ result += "//";
+
+ result += comment.m_text;
+
+ if (comment.isSyntheticMultiline())
+ result = result.replace("\n","\n" + formatLine("//", false));
+
+ if (comment.m_location == Comment::Location::Back_Inline)
+ result.prepend(" ");
+
+ if (useMultilineComment)
+ result += "*/";
+
+ return result;
+}
+
+QString DumpAstVisitor::getComment(Node *node, Comment::Location location) const
+{
+ const auto& comments = m_comment->attachedComments();
+ if (!comments.contains(node))
+ return "";
+
+ auto comment = comments[node];
+
+ if (comment.m_location != location)
+ return "";
+
+ return formatComment(comment);
+}
+
+QString DumpAstVisitor::getListItemComment(SourceLocation srcLocation,
+ Comment::Location location) const {
+ const auto& comments = m_comment->listComments();
+
+ if (!comments.contains(srcLocation.begin()))
+ return "";
+
+ auto comment = comments[srcLocation.begin()];
+
+ if (comment.m_location != location)
+ return "";
+
+ return formatComment(comment);
+}
+
+QString DumpAstVisitor::getOrphanedComments(Node *node) const {
+ const auto& orphans = m_comment->orphanComments()[node];
+
+ if (orphans.size() == 0)
+ return "";
+
+ QString result = "";
+
+ for (const Comment& orphan : orphans) {
+ result += formatLine(formatComment(orphan));
+ }
+
+ result += "\n";
+
+ return result;
+}
+
+QString DumpAstVisitor::parseArgumentList(ArgumentList *list)
+{
+ QString result = "";
+
+ for (auto *item = list; item != nullptr; item = item->next)
+ result += parseExpression(item->expression) + (item->next != nullptr ? ", " : "");
+
+ return result;
+}
+
+QString DumpAstVisitor::parseUiParameterList(UiParameterList *list) {
+ QString result = "";
+
+ for (auto *item = list; item != nullptr; item = item->next)
+ result += item->type->name + " " + item->name + (item->next != nullptr ? ", " : "");
+
+ return result;
+}
+
+QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope)
+{
+ switch (element->type)
+ {
+ case PatternElement::Literal:
+ return parseExpression(element->initializer);
+ case PatternElement::Binding: {
+ QString result = "";
+ QString expr = parseExpression(element->initializer);
+
+ if (scope) {
+ switch (element->scope) {
+ case VariableScope::NoScope:
+ break;
+ case VariableScope::Let:
+ result = "let ";
+ break;
+ case VariableScope::Const:
+ result = "const ";
+ break;
+ case VariableScope::Var:
+ result = "var ";
+ break;
+ }
+ }
+
+ result += element->bindingIdentifier.toString();
+
+ if (!expr.isEmpty())
+ result += " = "+expr;
+
+ return result;
+ }
+ default:
+ return "pe_unknown";
+ }
+}
+
+QString DumpAstVisitor::parsePatternElementList(PatternElementList *list)
+{
+ QString result = "";
+
+ for (auto *item = list; item != nullptr; item = item->next)
+ result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : "");
+
+ return result;
+}
+
+QString DumpAstVisitor::parseFormalParameterList(FormalParameterList *list)
+{
+ QString result = "";
+
+ for (auto *item = list; item != nullptr; item = item->next)
+ result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : "");
+
+ return result;
+}
+
+QString DumpAstVisitor::parseExpression(ExpressionNode *expression)
+{
+ if (expression == nullptr)
+ return "";
+
+ switch (expression->kind)
+ {
+ case Node::Kind_ArrayPattern:
+ return "["+parsePatternElementList(cast<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 "\""+cast<StringLiteral *>(expression)->value+"\"";
+ case Node::Kind_BinaryExpression: {
+ auto *binExpr = expression->binaryExpressionCast();
+ return parseExpression(binExpr->left) + " " + operatorToString(binExpr->op)
+ + " " + parseExpression(binExpr->right);
+ }
+ case Node::Kind_CallExpression: {
+ auto *callExpr = cast<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->next == nullptr && allowBraceless;
+
+ QString result = hasOneLine ? "\n" : "{\n";
+ m_indentLevel++;
+ result += parseStatementList(block->statements);
+ m_indentLevel--;
+
+ if (hasNext)
+ result += formatLine(hasOneLine ? "" : "} ", false);
+
+ if (!hasNext && !hasOneLine)
+ result += formatLine("}", false);
+
+ m_blockNeededBraces |= (block->statements->next != nullptr);
+
+ return result;
+}
+
+QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext,
+ bool blockAllowBraceless)
+{
+ if (statement == nullptr)
+ return "";
+
+ switch (statement->kind)
+ {
+ case Node::Kind_EmptyStatement:
+ return "";
+ case Node::Kind_ExpressionStatement:
+ return parseExpression(cast<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 if_false = parseStatement(ifStatement->ko, false, true);
+ QString if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), true);
+
+ if (m_blockNeededBraces) {
+ if_false = parseStatement(ifStatement->ko, false, false);
+ if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), false);
+ }
+
+ QString result = "if (" + parseExpression(ifStatement->expression) + ") ";
+
+ result += if_true;
+
+ if (!if_false.isEmpty())
+ {
+ result += "else " + if_false;
+ }
+
+ return result;
+ }
+ case Node::Kind_ForStatement: {
+ auto *forStatement = cast<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 (";
+
+ result += parsePatternElement(cast<PatternElement *>(forEachStatement->lhs));
+
+ switch (forEachStatement->type)
+ {
+ case ForEachType::In:
+ result += " in ";
+ break;
+ case ForEachType::Of:
+ result += " of ";
+ break;
+ }
+
+ result += parseExpression(forEachStatement->expression) + ") ";
+
+ result += parseStatement(forEachStatement->statement);
+
+ return result;
+ }
+ case Node::Kind_WhileStatement: {
+ auto *whileStatement = cast<WhileStatement *>(statement);
+
+ return "while ("+parseExpression(whileStatement->expression) + ") "
+ + parseStatement(whileStatement->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 ? "}," : "}");
+ if (!m_inArrayBinding)
+ addNewLine();
+}
+
+bool DumpAstVisitor::visit(UiEnumDeclaration *node) {
+
+ addNewLine();
+
+ addLine(getComment(node, Comment::Location::Front));
+ addLine("enum " + node->name + " {");
+ m_indentLevel++;
+ m_result += getOrphanedComments(node);
+
+ return true;
+}
+
+void DumpAstVisitor::endVisit(UiEnumDeclaration *) {
+ m_indentLevel--;
+ addLine("}");
+
+ addNewLine();
+}
+
+bool DumpAstVisitor::visit(UiEnumMemberList *node) {
+ for (auto *members = node; members != nullptr; members = members->next) {
+
+ addLine(getListItemComment(members->memberToken, Comment::Location::Front));
+
+ QString line = members->member.toString();
+
+ if (members->valueToken.isValid())
+ line += " = "+QString::number(members->value);
+
+ if (members->next != nullptr)
+ line += ",";
+
+ line += getListItemComment(members->memberToken, Comment::Location::Back_Inline);
+
+ addLine(line);
+ }
+
+ return true;
+}
+
+bool DumpAstVisitor::visit(UiScriptBinding *node) {
+ if (m_firstBinding) {
+ if (m_firstOfAll)
+ m_firstOfAll = false;
+ else
+ addNewLine();
+
+ m_firstBinding = false;
+ }
+
+ addLine(getComment(node, Comment::Location::Front));
+ addLine(parseUiQualifiedId(node->qualifiedId)+ ": " + parseStatement(node->statement)
+ + getComment(node, Comment::Location::Back_Inline));
+ return true;
+}
+
+bool DumpAstVisitor::visit(UiArrayBinding *node) {
+ if (m_firstBinding) {
+ if (m_firstOfAll)
+ m_firstOfAll = false;
+ else
+ addNewLine();
+
+ m_firstBinding = false;
+ }
+
+ addLine(getComment(node, Comment::Location::Front));
+ addLine(parseUiQualifiedId(node->qualifiedId)+ ": [");
+
+ m_indentLevel++;
+ m_inArrayBinding = true;
+
+ for (auto *item = node->members; item != nullptr; item = item->next) {
+ if (item->next == nullptr)
+ m_lastInArrayBinding = item->member;
+ }
+
+ m_result += getOrphanedComments(node);
+
+ return true;
+}
+
+void DumpAstVisitor::endVisit(UiArrayBinding *) {
+ m_indentLevel--;
+ m_inArrayBinding = false;
+ m_lastInArrayBinding = nullptr;
+ addLine("]");
+}
+
+bool DumpAstVisitor::visit(FunctionDeclaration *node) {
+
+ addNewLine();
+
+ addLine(getComment(node, Comment::Location::Front));
+ addLine("function "+node->name+"("+parseFormalParameterList(node->formals)+") {");
+ m_indentLevel++;
+ m_result += parseStatementList(node->body);
+ m_indentLevel--;
+ addLine("}");
+
+ addNewLine();
+
+ return true;
+}
+
+bool DumpAstVisitor::visit(UiObjectBinding *node) {
+ if (m_firstObject) {
+ if (m_firstOfAll)
+ m_firstOfAll = false;
+ else
+ addNewLine();
+
+ m_firstObject = false;
+ }
+
+ QString name = parseUiQualifiedId(node->qualifiedTypeNameId);
+
+ QString result = name;
+
+ if (node->hasOnToken)
+ result += " on "+parseUiQualifiedId(node->qualifiedId);
+
+ addNewLine();
+ addLine(getComment(node, Comment::Location::Front));
+ addLine(result+" {");
+
+ m_indentLevel++;
+
+ return true;
+}
+
+void DumpAstVisitor::endVisit(UiObjectBinding *) {
+ m_indentLevel--;
+ addLine("}");
+
+ addNewLine();
+}
+
+bool DumpAstVisitor::visit(UiImport *node) {
+ addLine(getComment(node, Comment::Location::Front));
+
+ QString result = "import ";
+
+ if (!node->fileName.isEmpty())
+ result += node->fileName;
+ else
+ result += parseUiQualifiedId(node->importUri);
+
+ if (node->version) {
+ result += " " + QString::number(node->version->majorVersion) + "."
+ + QString::number(node->version->minorVersion);
+ }
+
+ if (node->asToken.isValid()) {
+ result +=" as " + node->importId;
+ }
+
+ result += getComment(node, Comment::Location::Back_Inline);
+
+ addLine(result);
+
+ return true;
+}
diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h
new file mode 100644
index 0000000000..1348ee62e7
--- /dev/null
+++ b/tools/qmlformat/dumpastvisitor.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DUMPAST_H
+#define DUMPAST_H
+
+#include <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=true);
+ QString parseStatementList(StatementList *list);
+
+ QString parseExpression(ExpressionNode *expression);
+
+ QString parsePatternElement(PatternElement *element, bool scope=true);
+ QString parsePatternElementList(PatternElementList *element);
+
+ QString parseArgumentList(ArgumentList *list);
+
+ QString parseUiParameterList(UiParameterList *list);
+
+ QString parseVariableDeclarationList(VariableDeclarationList *list);
+
+ QString parseCaseBlock(CaseBlock *block);
+ QString parseBlock(Block *block, bool hasNext, bool allowBraceless);
+
+ QString parseExportsList(ExportsList *list);
+ QString parseExportSpecifier(ExportSpecifier *specifier);
+
+ QString parseFormalParameterList(FormalParameterList *list);
+
+ int m_indentLevel = 0;
+
+ bool m_error = false;
+ bool m_blockNeededBraces = false;
+ bool m_inArrayBinding = false;
+
+ bool m_firstOfAll = false;
+ bool m_firstSignal = false;
+ bool m_firstProperty = false;
+ bool m_firstBinding = false;
+ bool m_firstObject = true;
+
+ UiObjectMember* m_lastInArrayBinding = nullptr;
+ QString m_result = "";
+ CommentAstVisitor *m_comment;
+};
+
+#endif // DUMPAST_H
diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp
new file mode 100644
index 0000000000..bca788d316
--- /dev/null
+++ b/tools/qmlformat/main.cpp
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <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