From 73189151b59430671630ae931ac42c0e18cdab55 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Fri, 13 Dec 2019 16:10:46 +0100 Subject: qtdeclarative/tools: Implement qmlformat qmlformat is a tool that formats qml files according to the QML coding conventions (https://doc.qt.io/qt-5/qml-codingconventions.html). Change-Id: I359c4bd3b51f60614535f0e857d7e0b21b1d9033 Reviewed-by: Ulf Hermann --- tools/qmlformat/commentastvisitor.cpp | 270 +++++++++ tools/qmlformat/commentastvisitor.h | 131 +++++ tools/qmlformat/dumpastvisitor.cpp | 927 ++++++++++++++++++++++++++++++ tools/qmlformat/dumpastvisitor.h | 123 ++++ tools/qmlformat/main.cpp | 164 ++++++ tools/qmlformat/qmlformat.pro | 17 + tools/qmlformat/restructureastvisitor.cpp | 178 ++++++ tools/qmlformat/restructureastvisitor.h | 50 ++ 8 files changed, 1860 insertions(+) create mode 100644 tools/qmlformat/commentastvisitor.cpp create mode 100644 tools/qmlformat/commentastvisitor.h create mode 100644 tools/qmlformat/dumpastvisitor.cpp create mode 100644 tools/qmlformat/dumpastvisitor.h create mode 100644 tools/qmlformat/main.cpp create mode 100644 tools/qmlformat/qmlformat.pro create mode 100644 tools/qmlformat/restructureastvisitor.cpp create mode 100644 tools/qmlformat/restructureastvisitor.h (limited to 'tools/qmlformat') diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp new file mode 100644 index 0000000000..6d2bf49ddc --- /dev/null +++ b/tools/qmlformat/commentastvisitor.cpp @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "commentastvisitor.h" + +CommentAstVisitor::CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode) : m_engine(engine) +{ + rootNode->accept(this); + + // Look for complete orphans that have not been attached to *any* node + QVector completeOrphans; + + for (const auto &comment : m_engine->comments()) { + if (isCommentAttached(comment)) + continue; + + bool found_orphan = false; + for (const auto &orphanList : orphanComments().values()) { + for (const auto &orphan : orphanList) { + if (orphan.contains(comment)) { + found_orphan = true; + break; + } + } + + if (found_orphan) + break; + } + + if (found_orphan) + continue; + + completeOrphans.append(Comment(m_engine, Comment::Location::Front, {comment})); + } + + m_orphanComments[nullptr] = completeOrphans; +} + +QList CommentAstVisitor::findCommentsInLine(quint32 line, bool includePrevious) const +{ + QList results; + if (line == 0) + return results; + + for (const auto &location : m_engine->comments()) { + if (location.startLine != line) + continue; + + if (isCommentAttached(location)) + continue; + + results.append(location); + + if (includePrevious) { + // See if we can find any more comments above this one + auto previous = findCommentsInLine(line - 1, true); + + // Iterate it in reverse to restore the correct order + for (auto it = previous.rbegin(); it != previous.rend(); it++) { + results.prepend(*it); + } + } + + break; + } + + return results; +} + +bool CommentAstVisitor::isCommentAttached(const SourceLocation &location) const +{ + for (const auto &value : m_attachedComments.values()) { + if (value.contains(location)) + return true; + } + + for (const auto &value : m_listItemComments.values()) { + if (value.contains(location)) + return true; + } + + // If a comment is already marked as an orphan of a Node that counts as attached too. + for (const auto &orphanList : m_orphanComments.values()) { + for (const auto &value : orphanList) { + if (value.contains(location)) + return true; + } + } + + return false; +} + +Comment CommentAstVisitor::findComment(SourceLocation first, SourceLocation last, + int locations) const +{ + if (locations & Comment::Location::Front) { + quint32 searchAt = first.startLine - 1; + + const auto comments = findCommentsInLine(searchAt, true); + if (!comments.isEmpty()) + return Comment(m_engine, Comment::Location::Front, comments); + } + + if (locations & Comment::Location::Back_Inline) { + quint32 searchAt = last.startLine; + + const auto comments = findCommentsInLine(searchAt); + if (!comments.isEmpty()) + return Comment(m_engine, Comment::Location::Back_Inline, comments); + } + + if (locations & Comment::Location::Back) { + quint32 searchAt = last.startLine + 1; + + const auto comments = findCommentsInLine(searchAt); + if (!comments.isEmpty()) + return Comment(m_engine, Comment::Location::Back, comments); + } + + return Comment(); + +} + +Comment CommentAstVisitor::findComment(Node *node, int locations) const +{ + return findComment(node->firstSourceLocation(), node->lastSourceLocation(), locations); +} + +QVector CommentAstVisitor::findOrphanComments(Node *node) const +{ + QVector comments; + + for (auto &comment : m_engine->comments()) { + if (isCommentAttached(comment)) + continue; + + if (comment.begin() <= node->firstSourceLocation().begin() + || comment.end() > node->lastSourceLocation().end()) { + continue; + } + + comments.append(Comment(m_engine, Comment::Location::Front, {comment})); + } + + return comments; +} + +void CommentAstVisitor::attachComment(Node *node, int locations) +{ + auto comment = findComment(node, locations); + + if (comment.isValid()) + m_attachedComments[node] = comment; +} + +bool CommentAstVisitor::visit(UiScriptBinding *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(StatementList *node) +{ + for (auto *item = node; item != nullptr; item = item->next) + attachComment(item->statement, Comment::Front | Comment::Back_Inline); + return true; +} + +void CommentAstVisitor::endVisit(StatementList *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiObjectBinding *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(UiObjectDefinition *node) +{ + attachComment(node); + return true; +} + +void CommentAstVisitor::endVisit(UiObjectDefinition *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiArrayBinding *node) +{ + attachComment(node); + return true; +} + +void CommentAstVisitor::endVisit(UiArrayBinding *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiEnumDeclaration *node) +{ + attachComment(node); + return true; +} + +void CommentAstVisitor::endVisit(UiEnumDeclaration *node) +{ + m_orphanComments[node] = findOrphanComments(node); +} + +bool CommentAstVisitor::visit(UiEnumMemberList *node) +{ + for (auto *item = node; item != nullptr; item = item->next) { + auto comment = findComment(item->memberToken, + item->valueToken.isValid() ? item->valueToken : item->memberToken, + Comment::Front | Comment::Back_Inline); + + if (comment.isValid()) + m_listItemComments[item->memberToken.begin()] = comment; + } + + m_orphanComments[node] = findOrphanComments(node); + + return true; +} + +bool CommentAstVisitor::visit(UiPublicMember *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(FunctionDeclaration *node) +{ + attachComment(node); + return true; +} + +bool CommentAstVisitor::visit(UiImport *node) +{ + attachComment(node); + return true; +} diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h new file mode 100644 index 0000000000..dee2bb3983 --- /dev/null +++ b/tools/qmlformat/commentastvisitor.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef COMMENTASTVISITOR_H +#define COMMENTASTVISITOR_H + +#include +#include +#include + +#include +#include +#include + +using namespace QQmlJS::AST; + +struct Comment +{ + enum Location : int + { + Front = 1, + Back = Front << 1, + Back_Inline = Back << 1, + DefaultLocations = Front | Back_Inline, + AllLocations = Front | Back | Back_Inline + } m_location; + + Comment() {} + Comment(const QQmlJS::Engine *engine, Location location, QList srcLocations) + : m_location(location), m_srcLocations(srcLocations) { + for (const auto& srcLoc : srcLocations) { + m_text += engine->code().mid(static_cast(srcLoc.begin()), + static_cast(srcLoc.end() - srcLoc.begin())) + "\n"; + } + + m_text.chop(1); + } + + QList m_srcLocations; + + bool isValid() const { return !m_srcLocations.isEmpty(); } + bool isMultiline() const { return m_text.contains("\n"); } + bool isSyntheticMultiline() const { return m_srcLocations.size() > 1; } + + bool contains(const SourceLocation& location) const { + for (const SourceLocation& srcLoc : m_srcLocations) { + if (srcLoc.begin() == location.begin() && srcLoc.end() == location.end()) + return true; + } + + return false; + } + + QString m_text; +}; + +class CommentAstVisitor : protected Visitor +{ +public: + CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode); + + void throwRecursionDepthError() override {} + + const QHash attachedComments() const { return m_attachedComments; } + const QHash listComments() const { return m_listItemComments; } + const QHash> orphanComments() const { return m_orphanComments; } + + bool visit(UiScriptBinding *node) override; + bool visit(UiObjectBinding *node) override; + + bool visit(UiArrayBinding *node) override; + void endVisit(UiArrayBinding *node) override; + + bool visit(UiObjectDefinition *node) override; + void endVisit(UiObjectDefinition *) override; + + bool visit(UiEnumDeclaration *node) override; + void endVisit(UiEnumDeclaration *node) override; + + bool visit(UiEnumMemberList *node) override; + + bool visit(StatementList *node) override; + void endVisit(StatementList *node) override; + + bool visit(UiImport *node) override; + bool visit(UiPublicMember *node) override; + bool visit(FunctionDeclaration *node) override; +private: + bool isCommentAttached(const SourceLocation& location) const; + + QList findCommentsInLine(quint32 line, bool includePrevious=false) const; + + Comment findComment(SourceLocation first, SourceLocation last, + int locations = Comment::DefaultLocations) const; + + Comment findComment(Node *node, int locations = Comment::DefaultLocations) const; + QVector findOrphanComments(Node *node) const; + void attachComment(Node *node, int locations = Comment::DefaultLocations); + + QQmlJS::Engine *m_engine; + QHash m_attachedComments; + QHash m_listItemComments; + QHash> m_orphanComments; +}; + +#endif // COMMENTASTVISITOR_H diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp new file mode 100644 index 0000000000..09bb45c2bb --- /dev/null +++ b/tools/qmlformat/dumpastvisitor.cpp @@ -0,0 +1,927 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "dumpastvisitor.h" + +DumpAstVisitor::DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment): m_comment(comment) +{ + // Add all completely orphaned comments + m_result += getOrphanedComments(nullptr); + + rootNode->accept(this); +} + +static QString parseUiQualifiedId(UiQualifiedId *id) +{ + QString name = id->name.toString(); + for (auto *item = id->next; item != nullptr; item = item->next) { + name += "." + item->name; + } + + return name; +} + +static QString operatorToString(int op) +{ + switch (op) + { + case QSOperator::Add: return "+"; + case QSOperator::And: return "&&"; + case QSOperator::InplaceAnd: return "&="; + case QSOperator::Assign: return "="; + case QSOperator::BitAnd: return "&"; + case QSOperator::BitOr: return "|"; + case QSOperator::BitXor: return "^"; + case QSOperator::InplaceSub: return "-="; + case QSOperator::Div: return "/"; + case QSOperator::InplaceDiv: return "/="; + case QSOperator::Equal: return "=="; + case QSOperator::Exp: return "**"; + case QSOperator::InplaceExp: return "**="; + case QSOperator::Ge: return ">="; + case QSOperator::Gt: return ">"; + case QSOperator::In: return "in"; + case QSOperator::InplaceAdd: return "+="; + case QSOperator::InstanceOf: return "instanceof"; + case QSOperator::Le: return "<="; + case QSOperator::LShift: return "<<"; + case QSOperator::InplaceLeftShift: return "<<="; + case QSOperator::Lt: return "<"; + case QSOperator::Mod: return "%"; + case QSOperator::InplaceMod: return "%="; + case QSOperator::Mul: return "*"; + case QSOperator::InplaceMul: return "*="; + case QSOperator::NotEqual: return "!="; + case QSOperator::Or: return "||"; + case QSOperator::InplaceOr: return "|="; + case QSOperator::RShift: return ">>"; + case QSOperator::InplaceRightShift: return ">>="; + case QSOperator::StrictEqual: return "==="; + case QSOperator::StrictNotEqual: return "!=="; + case QSOperator::Sub: return "-"; + case QSOperator::URShift: return ">>>"; + case QSOperator::InplaceURightShift: return ">>>="; + case QSOperator::InplaceXor: return "^="; + case QSOperator::As: return "as"; + case QSOperator::Coalesce: return "??"; + case QSOperator::Invalid: + default: + return "INVALID"; + } +} + +QString DumpAstVisitor::formatComment(const Comment &comment) const +{ + QString result; + + bool useMultilineComment = comment.isMultiline() && !comment.isSyntheticMultiline(); + + if (useMultilineComment) + result += "/*"; + else + result += "//"; + + result += comment.m_text; + + if (comment.isSyntheticMultiline()) + result = result.replace("\n","\n" + formatLine("//", false)); + + if (comment.m_location == Comment::Location::Back_Inline) + result.prepend(" "); + + if (useMultilineComment) + result += "*/"; + + return result; +} + +QString DumpAstVisitor::getComment(Node *node, Comment::Location location) const +{ + const auto& comments = m_comment->attachedComments(); + if (!comments.contains(node)) + return ""; + + auto comment = comments[node]; + + if (comment.m_location != location) + return ""; + + return formatComment(comment); +} + +QString DumpAstVisitor::getListItemComment(SourceLocation srcLocation, + Comment::Location location) const { + const auto& comments = m_comment->listComments(); + + if (!comments.contains(srcLocation.begin())) + return ""; + + auto comment = comments[srcLocation.begin()]; + + if (comment.m_location != location) + return ""; + + return formatComment(comment); +} + +QString DumpAstVisitor::getOrphanedComments(Node *node) const { + const auto& orphans = m_comment->orphanComments()[node]; + + if (orphans.size() == 0) + return ""; + + QString result = ""; + + for (const Comment& orphan : orphans) { + result += formatLine(formatComment(orphan)); + } + + result += "\n"; + + return result; +} + +QString DumpAstVisitor::parseArgumentList(ArgumentList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += parseExpression(item->expression) + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parseUiParameterList(UiParameterList *list) { + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += item->type->name + " " + item->name + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope) +{ + switch (element->type) + { + case PatternElement::Literal: + return parseExpression(element->initializer); + case PatternElement::Binding: { + QString result = ""; + QString expr = parseExpression(element->initializer); + + if (scope) { + switch (element->scope) { + case VariableScope::NoScope: + break; + case VariableScope::Let: + result = "let "; + break; + case VariableScope::Const: + result = "const "; + break; + case VariableScope::Var: + result = "var "; + break; + } + } + + result += element->bindingIdentifier.toString(); + + if (!expr.isEmpty()) + result += " = "+expr; + + return result; + } + default: + return "pe_unknown"; + } +} + +QString DumpAstVisitor::parsePatternElementList(PatternElementList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parseFormalParameterList(FormalParameterList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) + result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : ""); + + return result; +} + +QString DumpAstVisitor::parseExpression(ExpressionNode *expression) +{ + if (expression == nullptr) + return ""; + + switch (expression->kind) + { + case Node::Kind_ArrayPattern: + return "["+parsePatternElementList(cast(expression)->elements)+"]"; + case Node::Kind_IdentifierExpression: + return cast(expression)->name.toString(); + case Node::Kind_FieldMemberExpression: { + auto *fieldMemberExpr = cast(expression); + return parseExpression(fieldMemberExpr->base) + "." + fieldMemberExpr->name.toString(); + } + case Node::Kind_ArrayMemberExpression: { + auto *arrayMemberExpr = cast(expression); + return parseExpression(arrayMemberExpr->base) + + "[" + parseExpression(arrayMemberExpr->expression) + "]"; + } + case Node::Kind_NestedExpression: + return "("+parseExpression(cast(expression)->expression)+")"; + case Node::Kind_TrueLiteral: + return "true"; + case Node::Kind_FalseLiteral: + return "false"; + case Node::Kind_FunctionExpression: + { + auto *functExpr = cast(expression); + + m_indentLevel++; + QString result = "function"; + + if (!functExpr->name.isEmpty()) + result += " " + functExpr->name; + + result += "("+parseFormalParameterList(functExpr->formals)+") {\n" + + parseStatementList(functExpr->body); + + m_indentLevel--; + + result += formatLine("}", false); + + return result; + } + case Node::Kind_NullExpression: + return "null"; + case Node::Kind_PostIncrementExpression: + return parseExpression(cast(expression)->base)+"++"; + case Node::Kind_PreIncrementExpression: + return "++"+parseExpression(cast(expression)->expression); + case Node::Kind_PostDecrementExpression: + return parseExpression(cast(expression)->base)+"--"; + case Node::Kind_PreDecrementExpression: + return "--"+parseExpression(cast(expression)->expression); + case Node::Kind_NumericLiteral: + return QString::number(cast(expression)->value); + case Node::Kind_StringLiteral: + return "\""+cast(expression)->value+"\""; + case Node::Kind_BinaryExpression: { + auto *binExpr = expression->binaryExpressionCast(); + return parseExpression(binExpr->left) + " " + operatorToString(binExpr->op) + + " " + parseExpression(binExpr->right); + } + case Node::Kind_CallExpression: { + auto *callExpr = cast(expression); + + return parseExpression(callExpr->base) + "(" + parseArgumentList(callExpr->arguments) + ")"; + } + case Node::Kind_NewExpression: + return "new "+parseExpression(cast(expression)->expression); + case Node::Kind_NewMemberExpression: { + auto *newMemberExpression = cast(expression); + return "new "+parseExpression(newMemberExpression->base) + + "(" +parseArgumentList(newMemberExpression->arguments)+")"; + } + case Node::Kind_DeleteExpression: + return "delete " + parseExpression(cast(expression)->expression); + case Node::Kind_VoidExpression: + return "void " + parseExpression(cast(expression)->expression); + case Node::Kind_TypeOfExpression: + return "typeof " + parseExpression(cast(expression)->expression); + case Node::Kind_UnaryPlusExpression: + return "+" + parseExpression(cast(expression)->expression); + case Node::Kind_UnaryMinusExpression: + return "-" + parseExpression(cast(expression)->expression); + case Node::Kind_NotExpression: + return "!" + parseExpression(cast(expression)->expression); + case Node::Kind_TildeExpression: + return "~" + parseExpression(cast(expression)->expression); + case Node::Kind_ConditionalExpression: { + auto *condExpr = cast(expression); + + QString result = ""; + + result += parseExpression(condExpr->expression) + " ? "; + result += parseExpression(condExpr->ok) + ": "; + result += parseExpression(condExpr->ko); + + return result; + } + case Node::Kind_YieldExpression: { + auto *yieldExpr = cast(expression); + + QString result = "yield"; + + if (yieldExpr->isYieldStar) + result += "*"; + + if (yieldExpr->expression) + result += " " + parseExpression(yieldExpr->expression); + + return result; + } + default: + m_error = true; + return "unknown_expression_"+QString::number(expression->kind); + } +} + +QString DumpAstVisitor::parseVariableDeclarationList(VariableDeclarationList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) { + result += parsePatternElement(item->declaration, (item == list)) + + (item->next != nullptr ? ", " : ""); + } + + return result; +} + +QString DumpAstVisitor::parseCaseBlock(CaseBlock *block) +{ + QString result = "{\n"; + + for (auto *item = block->clauses; item != nullptr; item = item->next) { + result += formatLine("case "+parseExpression(item->clause->expression)+":"); + m_indentLevel++; + result += parseStatementList(item->clause->statements); + m_indentLevel--; + } + + if (block->defaultClause) { + result += formatLine("default:"); + m_indentLevel++; + result += parseStatementList(block->defaultClause->statements); + m_indentLevel--; + } + + result += formatLine("}", false); + + return result; +} + +QString DumpAstVisitor::parseExportSpecifier(ExportSpecifier *specifier) +{ + QString result = specifier->identifier.toString(); + + if (!specifier->exportedIdentifier.isEmpty()) + result += " as " + specifier->exportedIdentifier; + + return result; +} + +QString DumpAstVisitor::parseExportsList(ExportsList *list) +{ + QString result = ""; + + for (auto *item = list; item != nullptr; item = item->next) { + result += formatLine(parseExportSpecifier(item->exportSpecifier) + + (item->next != nullptr ? "," : "")); + } + + return result; +} + +QString DumpAstVisitor::parseBlock(Block *block, bool hasNext, bool allowBraceless) +{ + bool hasOneLine = block->statements->next == nullptr && allowBraceless; + + QString result = hasOneLine ? "\n" : "{\n"; + m_indentLevel++; + result += parseStatementList(block->statements); + m_indentLevel--; + + if (hasNext) + result += formatLine(hasOneLine ? "" : "} ", false); + + if (!hasNext && !hasOneLine) + result += formatLine("}", false); + + m_blockNeededBraces |= (block->statements->next != nullptr); + + return result; +} + +QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, + bool blockAllowBraceless) +{ + if (statement == nullptr) + return ""; + + switch (statement->kind) + { + case Node::Kind_EmptyStatement: + return ""; + case Node::Kind_ExpressionStatement: + return parseExpression(cast(statement)->expression); + case Node::Kind_VariableStatement: + return parseVariableDeclarationList(cast(statement)->declarations); + case Node::Kind_ReturnStatement: + return "return "+parseExpression(cast(statement)->expression); + case Node::Kind_ContinueStatement: + return "continue"; + case Node::Kind_BreakStatement: + return "break"; + case Node::Kind_SwitchStatement: { + auto *switchStatement = cast(statement); + + QString result = "switch ("+parseExpression(switchStatement->expression)+") "; + + result += parseCaseBlock(switchStatement->block); + + return result; + } + case Node::Kind_IfStatement: { + auto *ifStatement = cast(statement); + + m_blockNeededBraces = false; + + QString if_false = parseStatement(ifStatement->ko, false, true); + QString if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), true); + + if (m_blockNeededBraces) { + if_false = parseStatement(ifStatement->ko, false, false); + if_true = parseStatement(ifStatement->ok, !if_false.isEmpty(), false); + } + + QString result = "if (" + parseExpression(ifStatement->expression) + ") "; + + result += if_true; + + if (!if_false.isEmpty()) + { + result += "else " + if_false; + } + + return result; + } + case Node::Kind_ForStatement: { + auto *forStatement = cast(statement); + + QString expr = parseExpression(forStatement->expression); + QString result = "for ("; + + result += parseVariableDeclarationList(forStatement->declarations); + + result += "; "; + + result += parseExpression(forStatement->condition) + "; "; + result += parseExpression(forStatement->expression)+") "; + + result += parseStatement(forStatement->statement); + + + return result; + } + case Node::Kind_ForEachStatement: { + auto *forEachStatement = cast(statement); + + QString result = "for ("; + + result += parsePatternElement(cast(forEachStatement->lhs)); + + switch (forEachStatement->type) + { + case ForEachType::In: + result += " in "; + break; + case ForEachType::Of: + result += " of "; + break; + } + + result += parseExpression(forEachStatement->expression) + ") "; + + result += parseStatement(forEachStatement->statement); + + return result; + } + case Node::Kind_WhileStatement: { + auto *whileStatement = cast(statement); + + return "while ("+parseExpression(whileStatement->expression) + ") " + + parseStatement(whileStatement->statement); + } + case Node::Kind_DoWhileStatement: { + auto *doWhileStatement = cast(statement); + return "do " + parseBlock(cast(doWhileStatement->statement), true, false) + + "while (" + parseExpression(doWhileStatement->expression) + ")"; + } + case Node::Kind_TryStatement: { + auto *tryStatement = cast(statement); + + Catch *catchExpr = tryStatement->catchExpression; + Finally *finallyExpr = tryStatement->finallyExpression; + + QString result; + + result += "try " + parseBlock(cast(tryStatement->statement), true, false); + + result += "catch (" + parsePatternElement(catchExpr->patternElement, false) + ") " + + parseBlock(cast(catchExpr->statement), finallyExpr, false); + + if (finallyExpr) { + result += "finally " + parseBlock(cast(tryStatement->statement), false, false); + } + + return result; + } + case Node::Kind_Block: { + return parseBlock(cast(statement), blockHasNext, blockAllowBraceless); + } + case Node::Kind_ThrowStatement: + return "throw "+parseExpression(cast(statement)->expression); + case Node::Kind_LabelledStatement: { + auto *labelledStatement = cast(statement); + QString result = labelledStatement->label+":\n"; + result += formatLine(parseStatement(labelledStatement->statement), false); + + return result; + } + case Node::Kind_WithStatement: { + auto *withStatement = cast(statement); + return "with (" + parseExpression(withStatement->expression) + ") " + + parseStatement(withStatement->statement); + } + case Node::Kind_DebuggerStatement: { + return "debugger"; + } + case Node::Kind_ExportDeclaration: + m_error = true; + return "export_decl_unsupported"; + case Node::Kind_ImportDeclaration: + m_error = true; + return "import_decl_unsupported"; + default: + m_error = true; + return "unknown_statement_"+QString::number(statement->kind); + } +} + +bool needsSemicolon(int kind) +{ + switch (kind) + { + case Node::Kind_ForStatement: + case Node::Kind_ForEachStatement: + case Node::Kind_IfStatement: + case Node::Kind_SwitchStatement: + case Node::Kind_WhileStatement: + case Node::Kind_DoWhileStatement: + case Node::Kind_TryStatement: + case Node::Kind_WithStatement: + return false; + default: + return true; + } +} + +QString DumpAstVisitor::parseStatementList(StatementList *list) +{ + QString result = ""; + + result += getOrphanedComments(list); + + for (auto *item = list; item != nullptr; item = item->next) { + QString statement = parseStatement(item->statement->statementCast()); + if (statement.isEmpty()) + continue; + + QString commentFront = getComment(item->statement, Comment::Location::Front); + QString commentBackInline = getComment(item->statement, Comment::Location::Back_Inline); + + if (!commentFront.isEmpty()) + result += formatLine(commentFront); + + result += formatLine(statement + (needsSemicolon(item->statement->kind) ? ";" : "") + + commentBackInline); + } + + return result; +} + +bool DumpAstVisitor::visit(UiPublicMember *node) { + addLine(getComment(node, Comment::Location::Front)); + + QString commentBackInline = getComment(node, Comment::Location::Back_Inline); + + switch (node->type) + { + case UiPublicMember::Signal: + if (m_firstSignal) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstSignal = false; + } + + addLine("signal "+node->name.toString()+"("+parseUiParameterList(node->parameters) + ")" + + commentBackInline); + break; + case UiPublicMember::Property: { + if (m_firstProperty) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstProperty = false; + } + + const bool is_required = node->requiredToken.isValid(); + const bool is_default = node->defaultToken.isValid(); + const bool is_readonly = node->readonlyToken.isValid(); + + QString prefix = ""; + QString statement = parseStatement(node->statement); + + if (!statement.isEmpty()) + statement.prepend(": "); + + if (is_required) + prefix += "required "; + + if (is_default) + prefix += "default "; + + if (is_readonly) + prefix += "readonly "; + + addLine(prefix + "property " + node->memberType->name + " " + + node->name+statement + commentBackInline); + break; + } + } + + return true; +} + +QString DumpAstVisitor::generateIndent() const { + constexpr int IDENT_WIDTH = 4; + + QString indent = ""; + for (int i = 0; i < IDENT_WIDTH*m_indentLevel; i++) + indent += " "; + + return indent; +} + +QString DumpAstVisitor::formatLine(QString line, bool newline) const { + QString result = generateIndent() + line; + if (newline) + result += "\n"; + + return result; +} + +void DumpAstVisitor::addNewLine(bool always) { + if (!always && m_result.endsWith("\n\n")) + return; + + m_result += "\n"; +} + +void DumpAstVisitor::addLine(QString line) { + // addLine does not support empty lines, use addNewLine(true) for that + if (line.isEmpty()) + return; + + m_result += formatLine(line); +} + +bool DumpAstVisitor::visit(UiObjectDefinition *node) { + if (m_firstObject) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstObject = false; + } + + addLine(getComment(node, Comment::Location::Front)); + addLine(node->qualifiedTypeNameId->name+" {"); + + m_indentLevel++; + + m_firstProperty = true; + m_firstSignal = true; + m_firstBinding = true; + m_firstObject = true; + m_firstOfAll = true; + + m_result += getOrphanedComments(node); + + return true; +} + +void DumpAstVisitor::endVisit(UiObjectDefinition *node) { + m_indentLevel--; + addLine(m_inArrayBinding && m_lastInArrayBinding != node ? "}," : "}"); + if (!m_inArrayBinding) + addNewLine(); +} + +bool DumpAstVisitor::visit(UiEnumDeclaration *node) { + + addNewLine(); + + addLine(getComment(node, Comment::Location::Front)); + addLine("enum " + node->name + " {"); + m_indentLevel++; + m_result += getOrphanedComments(node); + + return true; +} + +void DumpAstVisitor::endVisit(UiEnumDeclaration *) { + m_indentLevel--; + addLine("}"); + + addNewLine(); +} + +bool DumpAstVisitor::visit(UiEnumMemberList *node) { + for (auto *members = node; members != nullptr; members = members->next) { + + addLine(getListItemComment(members->memberToken, Comment::Location::Front)); + + QString line = members->member.toString(); + + if (members->valueToken.isValid()) + line += " = "+QString::number(members->value); + + if (members->next != nullptr) + line += ","; + + line += getListItemComment(members->memberToken, Comment::Location::Back_Inline); + + addLine(line); + } + + return true; +} + +bool DumpAstVisitor::visit(UiScriptBinding *node) { + if (m_firstBinding) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstBinding = false; + } + + addLine(getComment(node, Comment::Location::Front)); + addLine(parseUiQualifiedId(node->qualifiedId)+ ": " + parseStatement(node->statement) + + getComment(node, Comment::Location::Back_Inline)); + return true; +} + +bool DumpAstVisitor::visit(UiArrayBinding *node) { + if (m_firstBinding) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstBinding = false; + } + + addLine(getComment(node, Comment::Location::Front)); + addLine(parseUiQualifiedId(node->qualifiedId)+ ": ["); + + m_indentLevel++; + m_inArrayBinding = true; + + for (auto *item = node->members; item != nullptr; item = item->next) { + if (item->next == nullptr) + m_lastInArrayBinding = item->member; + } + + m_result += getOrphanedComments(node); + + return true; +} + +void DumpAstVisitor::endVisit(UiArrayBinding *) { + m_indentLevel--; + m_inArrayBinding = false; + m_lastInArrayBinding = nullptr; + addLine("]"); +} + +bool DumpAstVisitor::visit(FunctionDeclaration *node) { + + addNewLine(); + + addLine(getComment(node, Comment::Location::Front)); + addLine("function "+node->name+"("+parseFormalParameterList(node->formals)+") {"); + m_indentLevel++; + m_result += parseStatementList(node->body); + m_indentLevel--; + addLine("}"); + + addNewLine(); + + return true; +} + +bool DumpAstVisitor::visit(UiObjectBinding *node) { + if (m_firstObject) { + if (m_firstOfAll) + m_firstOfAll = false; + else + addNewLine(); + + m_firstObject = false; + } + + QString name = parseUiQualifiedId(node->qualifiedTypeNameId); + + QString result = name; + + if (node->hasOnToken) + result += " on "+parseUiQualifiedId(node->qualifiedId); + + addNewLine(); + addLine(getComment(node, Comment::Location::Front)); + addLine(result+" {"); + + m_indentLevel++; + + return true; +} + +void DumpAstVisitor::endVisit(UiObjectBinding *) { + m_indentLevel--; + addLine("}"); + + addNewLine(); +} + +bool DumpAstVisitor::visit(UiImport *node) { + addLine(getComment(node, Comment::Location::Front)); + + QString result = "import "; + + if (!node->fileName.isEmpty()) + result += node->fileName; + else + result += parseUiQualifiedId(node->importUri); + + if (node->version) { + result += " " + QString::number(node->version->majorVersion) + "." + + QString::number(node->version->minorVersion); + } + + if (node->asToken.isValid()) { + result +=" as " + node->importId; + } + + result += getComment(node, Comment::Location::Back_Inline); + + addLine(result); + + return true; +} diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h new file mode 100644 index 0000000000..1348ee62e7 --- /dev/null +++ b/tools/qmlformat/dumpastvisitor.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DUMPAST_H +#define DUMPAST_H + +#include +#include + +#include "commentastvisitor.h" + +using namespace QQmlJS::AST; + +class DumpAstVisitor : protected Visitor +{ +public: + DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment); + + QString toString() const { return m_result; } + + bool visit(UiScriptBinding *node) override; + + bool visit(UiArrayBinding *node) override; + void endVisit(UiArrayBinding *node) override; + + bool visit(UiObjectBinding *node) override; + void endVisit(UiObjectBinding *node) override; + + bool visit(FunctionDeclaration *node) override; + + bool visit(UiObjectDefinition *node) override; + void endVisit(UiObjectDefinition *node) override; + + bool visit(UiEnumDeclaration *node) override; + void endVisit(UiEnumDeclaration *node) override; + + bool visit(UiEnumMemberList *node) override; + bool visit(UiPublicMember *node) override; + bool visit(UiImport *node) override; + + void throwRecursionDepthError() override {} + + bool error() const { return m_error; } +private: + QString generateIndent() const; + QString formatLine(QString line, bool newline=true) const; + + QString formatComment(const Comment &comment) const; + + QString getComment(Node *node, Comment::Location location) const; + QString getListItemComment(SourceLocation srcLocation, Comment::Location location) const; + + void addNewLine(bool always=false); + void addLine(QString line); + + QString getOrphanedComments(Node *node) const; + + QString parseStatement(Statement *statement, bool blockHasNext=false, + bool blockAllowBraceless=true); + QString parseStatementList(StatementList *list); + + QString parseExpression(ExpressionNode *expression); + + QString parsePatternElement(PatternElement *element, bool scope=true); + QString parsePatternElementList(PatternElementList *element); + + QString parseArgumentList(ArgumentList *list); + + QString parseUiParameterList(UiParameterList *list); + + QString parseVariableDeclarationList(VariableDeclarationList *list); + + QString parseCaseBlock(CaseBlock *block); + QString parseBlock(Block *block, bool hasNext, bool allowBraceless); + + QString parseExportsList(ExportsList *list); + QString parseExportSpecifier(ExportSpecifier *specifier); + + QString parseFormalParameterList(FormalParameterList *list); + + int m_indentLevel = 0; + + bool m_error = false; + bool m_blockNeededBraces = false; + bool m_inArrayBinding = false; + + bool m_firstOfAll = false; + bool m_firstSignal = false; + bool m_firstProperty = false; + bool m_firstBinding = false; + bool m_firstObject = true; + + UiObjectMember* m_lastInArrayBinding = nullptr; + QString m_result = ""; + CommentAstVisitor *m_comment; +}; + +#endif // DUMPAST_H diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp new file mode 100644 index 0000000000..bca788d316 --- /dev/null +++ b/tools/qmlformat/main.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#if QT_CONFIG(commandlineparser) +#include +#endif + +#include "commentastvisitor.h" +#include "dumpastvisitor.h" +#include "restructureastvisitor.h" + +bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImports) +{ + QFile file(filename); + + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { + qWarning().noquote() << "Failed to open" << filename << "for reading."; + return false; + } + + QString code = QString::fromUtf8(file.readAll()); + file.close(); + + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + lexer.setCode(code, 1, true); + QQmlJS::Parser parser(&engine); + + bool success = parser.parse(); + + if (!success) { + const auto diagnosticMessages = parser.diagnosticMessages(); + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") + .arg(filename).arg(m.line).arg(m.message); + } + + qWarning().noquote() << "Failed to parse" << filename; + return false; + } + + // Try to attach comments to AST nodes + CommentAstVisitor comment(&engine, parser.rootNode()); + + if (verbose) + qWarning().noquote() << comment.attachedComments().size() << "comment(s) attached."; + + if (verbose) { + int orphaned = 0; + + for (const auto& orphanList : comment.orphanComments().values()) + orphaned += orphanList.size(); + + qWarning().noquote() << orphaned << "comments are orphans."; + } + + if (verbose && sortImports) + qWarning().noquote() << "Sorting imports"; + + // Do the actual restructuring + RestructureAstVisitor restructure(parser.rootNode(), sortImports); + + // Turn AST back into source code + if (verbose) + qWarning().noquote() << "Dumping" << filename; + + DumpAstVisitor dump(parser.rootNode(), &comment); + + if (dump.error()) + qWarning().noquote() << "An error has occurred. The output may not be reliable."; + + if (inplace) { + if (verbose) + qWarning().noquote() << "Writing to file" << filename; + + if (!file.open(QIODevice::Text | QIODevice::WriteOnly)) + { + qWarning().noquote() << "Failed to open" << filename << "for writing"; + return false; + } + + file.write(dump.toString().toUtf8()); + file.close(); + } else { + QTextStream(stdout) << dump.toString(); + } + + return true; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName("qmlformat"); + QCoreApplication::setApplicationVersion("1.0"); + + bool success = true; +#if QT_CONFIG(commandlineparser) + QCommandLineParser parser; + parser.setApplicationDescription("Formats QML files according to the QML Coding Conventions."); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addOption(QCommandLineOption({"V", "verbose"}, + QStringLiteral("Verbose mode. Outputs more detailed information."))); + + parser.addOption(QCommandLineOption({"n", "no-sort"}, + QStringLiteral("Do not sort imports."))); + + parser.addOption(QCommandLineOption({"i", "inplace"}, + QStringLiteral("Edit file in-place instead of outputting to stdout."))); + + parser.addPositionalArgument("filenames", "files to be processed by qmlformat"); + + parser.process(app); + + const auto positionalArguments = parser.positionalArguments(); + + if (positionalArguments.isEmpty()) + parser.showHelp(-1); + + for (const QString& file: parser.positionalArguments()) { + if (!parseFile(file, parser.isSet("inplace"), parser.isSet("verbose"), !parser.isSet("no-sort"))) + success = false; + } +#endif + + return success ? 0 : 1; +} diff --git a/tools/qmlformat/qmlformat.pro b/tools/qmlformat/qmlformat.pro new file mode 100644 index 0000000000..c8e74a4b55 --- /dev/null +++ b/tools/qmlformat/qmlformat.pro @@ -0,0 +1,17 @@ +option(host_build) + +QT = core qmldevtools-private + +SOURCES += main.cpp \ + commentastvisitor.cpp \ + dumpastvisitor.cpp \ + restructureastvisitor.cpp + +QMAKE_TARGET_DESCRIPTION = QML Formatter + +HEADERS += \ + commentastvisitor.h \ + dumpastvisitor.h \ + restructureastvisitor.h + +load(qt_tool) diff --git a/tools/qmlformat/restructureastvisitor.cpp b/tools/qmlformat/restructureastvisitor.cpp new file mode 100644 index 0000000000..f9ac2a20c2 --- /dev/null +++ b/tools/qmlformat/restructureastvisitor.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "restructureastvisitor.h" + +#include + +RestructureAstVisitor::RestructureAstVisitor(Node *rootNode, bool sortImports) : m_sortImports(sortImports) +{ + rootNode->accept(this); +} + +template +static QList findKind(UiObjectMemberList *list) +{ + QList members; + for (auto *item = list; item != nullptr; item = item->next) { + if (cast(item->member) != nullptr) + members.append(cast(item->member)); + } + + return members; +} + +template +static QList findKind(UiHeaderItemList *list) +{ + QList members; + for (auto *item = list; item != nullptr; item = item->next) { + if (cast(item->headerItem) != nullptr) + members.append(cast(item->headerItem)); + } + + return members; +} + +static QString parseUiQualifiedId(UiQualifiedId *id) +{ + QString name = id->name.toString(); + for (auto *item = id->next; item != nullptr; item = item->next) { + name += "." + item->name; + } + + return name; +} + +void RestructureAstVisitor::endVisit(UiHeaderItemList *node) +{ + QList correctOrder; + + auto imports = findKind(node); + + if (!m_sortImports) + return; + + // Sort imports + std::sort(imports.begin(), imports.end(), [](UiImport *a, UiImport *b) + { + auto nameA = a->fileName.isEmpty() ? parseUiQualifiedId(a->importUri) + : a->fileName.toString(); + auto nameB = b->fileName.isEmpty() ? parseUiQualifiedId(b->importUri) + : b->fileName.toString(); + + return nameA < nameB; + }); + + // Add imports + for (auto *import : imports) + correctOrder.append(import); + + // Add all the other items + for (auto *item = node; item != nullptr; item = item->next) { + if (!correctOrder.contains(item->headerItem)) + correctOrder.append(item->headerItem); + } + + // Rebuild member list from correctOrder + for (auto *item = node; item != nullptr; item = item->next) { + item->headerItem = correctOrder.front(); + correctOrder.pop_front(); + } +} + +void RestructureAstVisitor::endVisit(UiObjectMemberList *node) +{ + QList correctOrder; + + auto enumDeclarations = findKind(node); + auto scriptBindings = findKind(node); + auto arrayBindings = findKind(node); + auto publicMembers = findKind(node); + auto sourceElements = findKind(node); + auto objectDefinitions = findKind(node); + + // This structure is based on https://doc.qt.io/qt-5/qml-codingconventions.html + + // 1st id + for (auto *binding : scriptBindings) { + if (binding->qualifiedId->name == "id") { + correctOrder.append(binding); + + scriptBindings.removeOne(binding); + break; + } + } + + // 2nd enums + for (auto *enumDeclaration : enumDeclarations) + correctOrder.append(enumDeclaration); + + // 3rd property declarations + for (auto *publicMember : publicMembers) { + if (publicMember->type != UiPublicMember::Property) + continue; + + correctOrder.append(publicMember); + } + + // 4th signals + for (auto *publicMember : publicMembers) { + if (publicMember->type != UiPublicMember::Signal) + continue; + + correctOrder.append(publicMember); + } + + // 5th functions + for (auto *source : sourceElements) + correctOrder.append(source); + + // 6th properties + for (auto *binding : scriptBindings) + correctOrder.append(binding); + + for (auto *binding : arrayBindings) + correctOrder.append(binding); + + // 7th child objects + for (auto *objectDefinition : objectDefinitions) + correctOrder.append(objectDefinition); + + // 8th all the rest + for (auto *item = node; item != nullptr; item = item->next) { + if (!correctOrder.contains(item->member)) + correctOrder.append(item->member); + } + + // Rebuild member list from correctOrder + for (auto *item = node; item != nullptr; item = item->next) { + item->member = correctOrder.front(); + correctOrder.pop_front(); + } +} diff --git a/tools/qmlformat/restructureastvisitor.h b/tools/qmlformat/restructureastvisitor.h new file mode 100644 index 0000000000..a2195c8c2e --- /dev/null +++ b/tools/qmlformat/restructureastvisitor.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef RESTRUCTUREASTVISITOR_H +#define RESTRUCTUREASTVISITOR_H + +#include +#include + +using namespace QQmlJS::AST; + +class RestructureAstVisitor : protected Visitor +{ +public: + RestructureAstVisitor(Node *rootNode, bool sortImports); + + void throwRecursionDepthError() override {} + + void endVisit(UiObjectMemberList *node) override; + void endVisit(UiHeaderItemList *node) override; +private: + bool m_sortImports = false; +}; + +#endif // RESTRUCTUREASTVISITOR_H -- cgit v1.2.3