diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2020-01-17 14:28:09 +0100 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2020-01-30 08:24:40 +0100 |
commit | e080f48f905be597b1a645f1641b2b06553df6a2 (patch) | |
tree | eef0b5d503cf94ed217eab5008dff076e8833113 /tools | |
parent | 78a69fa05e3b2af6ed640692d81e2b1c355fe525 (diff) |
qmlformat: Support even more language features
Adds support (among other things) for:
- Pragmas
- Type annotations
- get / set properties
- Some previously unsupported escape sequences (\b,\v...)
- Calling methods on numeric literals
Also now checks whether the dumped code is still parsable.
Change-Id: Ia142a7c0b3e608115e79c1d98a62b682dce4eec9
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmlformat/commentastvisitor.cpp | 6 | ||||
-rw-r--r-- | tools/qmlformat/commentastvisitor.h | 1 | ||||
-rw-r--r-- | tools/qmlformat/dumpastvisitor.cpp | 367 | ||||
-rw-r--r-- | tools/qmlformat/dumpastvisitor.h | 31 | ||||
-rw-r--r-- | tools/qmlformat/main.cpp | 26 |
5 files changed, 330 insertions, 101 deletions
diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp index 8264a6099e..4dd241ff93 100644 --- a/tools/qmlformat/commentastvisitor.cpp +++ b/tools/qmlformat/commentastvisitor.cpp @@ -268,3 +268,9 @@ bool CommentAstVisitor::visit(UiImport *node) attachComment(node); return true; } + +bool CommentAstVisitor::visit(UiPragma *node) +{ + attachComment(node); + return true; +} diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h index 369784a5ba..c756c4f820 100644 --- a/tools/qmlformat/commentastvisitor.h +++ b/tools/qmlformat/commentastvisitor.h @@ -108,6 +108,7 @@ public: void endVisit(StatementList *node) override; bool visit(UiImport *node) override; + bool visit(UiPragma *node) override; bool visit(UiPublicMember *node) override; bool visit(FunctionDeclaration *node) override; private: diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp index 20ebef8927..75d9fa8a4f 100644 --- a/tools/qmlformat/dumpastvisitor.cpp +++ b/tools/qmlformat/dumpastvisitor.cpp @@ -35,10 +35,21 @@ DumpAstVisitor::DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment): m_co // Add all completely orphaned comments m_result += getOrphanedComments(nullptr); + m_scope_properties.push(ScopeProperties {}); + rootNode->accept(this); // We need to get rid of one new-line so our output doesn't append an empty line m_result.chop(1); + + // Remove trailing whitespace + QStringList lines = m_result.split("\n"); + for (QString& line : lines) { + while (line.endsWith(" ")) + line.chop(1); + } + + m_result = lines.join("\n"); } static QString parseUiQualifiedId(UiQualifiedId *id) @@ -185,7 +196,7 @@ 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 ? ", " : ""); + result += parseUiQualifiedId(item->type) + " " + item->name + (item->next != nullptr ? ", " : ""); return result; } @@ -218,20 +229,28 @@ QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope) result += element->bindingIdentifier.toString(); + if (element->typeAnnotation != nullptr) + result += ": " + parseType(element->typeAnnotation->type); + if (!expr.isEmpty()) result += " = "+expr; return result; } default: + m_error = true; return "pe_unknown"; } } QString escapeString(QString string) { - // Escape \r, \n and \t - string = string.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t"); + // Handle escape sequences + string = string.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t") + .replace("\b","\\b").replace("\v", "\\v").replace("\f", "\\f"); + + // Escape backslash + string = string.replace("\\", "\\\\"); // Escape " string = string.replace("\"", "\\\""); @@ -261,7 +280,14 @@ QString DumpAstVisitor::parseFormalParameterList(FormalParameterList *list) QString DumpAstVisitor::parsePatternProperty(PatternProperty *property) { - return escapeString(property->name->asString())+": "+parsePatternElement(property, false); + switch (property->type) { + case PatternElement::Getter: + return "get "+parseFunctionExpression(cast<FunctionExpression *>(property->initializer), true); + case PatternElement::Setter: + return "set "+parseFunctionExpression(cast<FunctionExpression *>(property->initializer), true); + default: + return escapeString(property->name->asString())+": "+parsePatternElement(property, false); + } } QString DumpAstVisitor::parsePatternPropertyList(PatternPropertyList *list) @@ -275,6 +301,61 @@ QString DumpAstVisitor::parsePatternPropertyList(PatternPropertyList *list) return result; } +QString DumpAstVisitor::parseFunctionExpression(FunctionExpression *functExpr, bool omitFunction) +{ + m_indentLevel++; + QString result; + + if (!functExpr->isArrowFunction) { + result += omitFunction ? "" : "function"; + + if (functExpr->isGenerator) + result += "*"; + + if (!functExpr->name.isEmpty()) + result += (omitFunction ? "" : " ") + functExpr->name; + + result += "("+parseFormalParameterList(functExpr->formals)+")"; + + if (functExpr->typeAnnotation != nullptr) + result += " : " + parseType(functExpr->typeAnnotation->type); + + result += " {\n" + parseStatementList(functExpr->body); + } else { + result += "("+parseFormalParameterList(functExpr->formals)+")"; + + if (functExpr->typeAnnotation != nullptr) + result += " : " + parseType(functExpr->typeAnnotation->type); + + result += " => {\n" + parseStatementList(functExpr->body); + } + + m_indentLevel--; + + result += formatLine("}", false); + + return result; + +} + +QString DumpAstVisitor::parseType(Type *type) { + QString result = parseUiQualifiedId(type->typeId); + + if (type->typeArguments != nullptr) { + TypeArgumentList *list = cast<TypeArgumentList *>(type->typeArguments); + + result += "<"; + + for (auto *item = list; item != nullptr; item = item->next) { + result += parseType(item->typeId) + (item->next != nullptr ? ", " : ""); + } + + result += ">"; + } + + return result; +} + QString DumpAstVisitor::parseExpression(ExpressionNode *expression) { if (expression == nullptr) @@ -288,7 +369,15 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression) return cast<IdentifierExpression*>(expression)->name.toString(); case Node::Kind_FieldMemberExpression: { auto *fieldMemberExpr = cast<FieldMemberExpression *>(expression); - return parseExpression(fieldMemberExpr->base) + "." + fieldMemberExpr->name.toString(); + QString result = parseExpression(fieldMemberExpr->base); + + // If we're operating on a numeric literal, always put it in braces + if (fieldMemberExpr->base->kind == Node::Kind_NumericLiteral) + result = "(" + result + ")"; + + result += "." + fieldMemberExpr->name.toString(); + + return result; } case Node::Kind_ArrayMemberExpression: { auto *arrayMemberExpr = cast<ArrayMemberExpression *>(expression); @@ -304,31 +393,7 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression) case Node::Kind_FunctionExpression: { auto *functExpr = cast<FunctionExpression *>(expression); - - m_indentLevel++; - QString result; - - if (!functExpr->isArrowFunction) { - result += "function"; - - if (functExpr->isGenerator) - result += "*"; - - if (!functExpr->name.isEmpty()) - result += " " + functExpr->name; - - result += "("+parseFormalParameterList(functExpr->formals)+") {\n" - + parseStatementList(functExpr->body); - } else { - result += "("+parseFormalParameterList(functExpr->formals)+") => {\n"; - result += parseStatementList(functExpr->body); - } - - m_indentLevel--; - - result += formatLine("}", false); - - return result; + return parseFunctionExpression(functExpr); } case Node::Kind_NullExpression: return "null"; @@ -419,8 +484,7 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression) } case Node::Kind_Type: { auto* type = reinterpret_cast<Type*>(expression); - - return parseUiQualifiedId(type->typeId); + return parseType(type); } case Node::Kind_RegExpLiteral: { auto* regexpLiteral = cast<RegExpLiteral*>(expression); @@ -610,10 +674,14 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, result += "; "; result += parseExpression(forStatement->condition) + "; "; - result += parseExpression(forStatement->expression)+") "; + result += parseExpression(forStatement->expression)+")"; - result += parseStatement(forStatement->statement); + const QString statement = parseStatement(forStatement->statement); + if (!statement.isEmpty()) + result += " "+statement; + else + result += ";"; return result; } @@ -639,9 +707,16 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, break; } - result += parseExpression(forEachStatement->expression) + ") "; + result += parseExpression(forEachStatement->expression) + ")"; + + const QString statement = parseStatement(forEachStatement->statement); + + if (!statement.isEmpty()) + result += " "+statement; + else + result += ";"; + - result += parseStatement(forEachStatement->statement); return result; } @@ -652,9 +727,14 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, auto statement = parseStatement(whileStatement->statement, false, true); - return "while ("+parseExpression(whileStatement->expression) + ")" - + (m_blockNeededBraces ? " " : "") - + statement; + QString result = "while ("+parseExpression(whileStatement->expression) + ")"; + + if (!statement.isEmpty()) + result += (m_blockNeededBraces ? " " : "") + statement; + else + result += ";"; + + return result; } case Node::Kind_DoWhileStatement: { auto *doWhileStatement = cast<DoWhileStatement *>(statement); @@ -762,31 +842,32 @@ bool DumpAstVisitor::visit(UiPublicMember *node) { switch (node->type) { case UiPublicMember::Signal: - if (m_firstSignal) { - if (m_firstOfAll) - m_firstOfAll = false; + if (scope().m_firstSignal) { + if (scope().m_firstOfAll) + scope().m_firstOfAll = false; else addNewLine(); - m_firstSignal = false; + scope().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; + if (scope().m_firstProperty) { + if (scope().m_firstOfAll) + scope().m_firstOfAll = false; else addNewLine(); - m_firstProperty = false; + scope().m_firstProperty = false; } const bool is_required = node->requiredToken.isValid(); const bool is_default = node->defaultToken.isValid(); const bool is_readonly = node->readonlyToken.isValid(); + const bool has_type_modifier = node->typeModifierToken.isValid(); QString prefix = ""; QString statement = parseStatement(node->statement); @@ -803,8 +884,20 @@ bool DumpAstVisitor::visit(UiPublicMember *node) { if (is_readonly) prefix += "readonly "; - addLine(prefix + "property " + node->memberType->name + " " - + node->name+statement + commentBackInline); + QString member_type = parseUiQualifiedId(node->memberType); + + if (has_type_modifier) + member_type = node->typeModifier + "<" + member_type + ">"; + + if (is_readonly && statement.isEmpty() + && scope().m_bindings.contains(node->name.toString())) { + m_result += formatLine(prefix + "property " + member_type + " ", false); + + scope().m_pendingBinding = true; + } else { + addLine(prefix + "property " + member_type + " " + + node->name+statement + commentBackInline); + } break; } } @@ -845,26 +938,70 @@ void DumpAstVisitor::addLine(QString line) { m_result += formatLine(line); } +QHash<QString, UiObjectMember*> findBindings(UiObjectMemberList *list) { + QHash<QString, UiObjectMember*> bindings; + + // This relies on RestructureASTVisitor having run beforehand + + for (auto *item = list; item != nullptr; item = item->next) { + switch (item->member->kind) { + case Node::Kind_UiPublicMember: { + UiPublicMember *member = cast<UiPublicMember *>(item->member); + + if (member->type != UiPublicMember::Property) + continue; + + bindings[member->name.toString()] = nullptr; + + break; + } + case Node::Kind_UiObjectBinding: { + UiObjectBinding *binding = cast<UiObjectBinding *>(item->member); + + const QString name = parseUiQualifiedId(binding->qualifiedId); + + if (bindings.contains(name)) + bindings[name] = binding; + + break; + } + case Node::Kind_UiArrayBinding: { + UiArrayBinding *binding = cast<UiArrayBinding *>(item->member); + + const QString name = parseUiQualifiedId(binding->qualifiedId); + + if (bindings.contains(name)) + bindings[name] = binding; + + break; + } + case Node::Kind_UiScriptBinding: + // We can ignore UiScriptBindings since those are actually properly attached to the property + break; + } + } + + return bindings; +} + bool DumpAstVisitor::visit(UiObjectDefinition *node) { - if (m_firstObject) { - if (m_firstOfAll) - m_firstOfAll = false; + if (scope().m_firstObject) { + if (scope().m_firstOfAll) + scope().m_firstOfAll = false; else addNewLine(); - m_firstObject = false; + scope().m_firstObject = false; } addLine(getComment(node, Comment::Location::Front)); - addLine(node->qualifiedTypeNameId->name+" {"); + addLine(parseUiQualifiedId(node->qualifiedTypeNameId) + " {"); m_indentLevel++; - m_firstProperty = true; - m_firstSignal = true; - m_firstBinding = true; - m_firstObject = true; - m_firstOfAll = true; + ScopeProperties props; + props.m_bindings = findBindings(node->initializer->members); + m_scope_properties.push(props); m_result += getOrphanedComments(node); @@ -873,9 +1010,14 @@ bool DumpAstVisitor::visit(UiObjectDefinition *node) { void DumpAstVisitor::endVisit(UiObjectDefinition *node) { m_indentLevel--; - addLine(m_inArrayBinding && m_lastInArrayBinding != node ? "}," : "}"); + + m_scope_properties.pop(); + + bool need_comma = scope().m_inArrayBinding && scope().m_lastInArrayBinding != node; + + addLine(need_comma ? "}," : "}"); addLine(getComment(node, Comment::Location::Back)); - if (!m_inArrayBinding) + if (!scope().m_inArrayBinding) addNewLine(); } @@ -920,49 +1062,64 @@ bool DumpAstVisitor::visit(UiEnumMemberList *node) { } bool DumpAstVisitor::visit(UiScriptBinding *node) { - if (m_firstBinding) { - if (m_firstOfAll) - m_firstOfAll = false; + if (scope().m_firstBinding) { + if (scope().m_firstOfAll) + scope().m_firstOfAll = false; else addNewLine(); if (parseUiQualifiedId(node->qualifiedId) != "id") - m_firstBinding = false; + scope().m_firstBinding = false; } addLine(getComment(node, Comment::Location::Front)); - addLine(parseUiQualifiedId(node->qualifiedId)+ ": " + parseStatement(node->statement) - + getComment(node, Comment::Location::Back_Inline)); + + QString statement = parseStatement(node->statement); + + QString result = parseUiQualifiedId(node->qualifiedId) + ":"; + + if (!statement.isEmpty()) + result += " "+statement; + else + result += ";"; + + result += getComment(node, Comment::Location::Back_Inline); + + addLine(result); + return true; } bool DumpAstVisitor::visit(UiArrayBinding *node) { - if (m_firstBinding) { - if (m_firstOfAll) - m_firstOfAll = false; + if (!scope().m_pendingBinding && scope().m_firstBinding) { + if (scope().m_firstOfAll) + scope().m_firstOfAll = false; else addNewLine(); - m_firstBinding = false; + scope().m_firstBinding = false; } - addLine(getComment(node, Comment::Location::Front)); - addLine(parseUiQualifiedId(node->qualifiedId)+ ": ["); + if (scope().m_pendingBinding) { + m_result += parseUiQualifiedId(node->qualifiedId)+ ": [\n"; + scope().m_pendingBinding = false; + } else { + addLine(getComment(node, Comment::Location::Front)); + addLine(parseUiQualifiedId(node->qualifiedId)+ ": ["); + } m_indentLevel++; - m_inArrayBinding = true; - m_firstOfAll = true; - m_firstObject = true; - m_firstSignal = true; - m_firstBinding = true; - m_firstProperty = true; + ScopeProperties props; + props.m_inArrayBinding = true; for (auto *item = node->members; item != nullptr; item = item->next) { if (item->next == nullptr) - m_lastInArrayBinding = item->member; + props.m_lastInArrayBinding = item->member; } + m_scope_properties.push(props); + m_result += getOrphanedComments(node); return true; @@ -970,8 +1127,7 @@ bool DumpAstVisitor::visit(UiArrayBinding *node) { void DumpAstVisitor::endVisit(UiArrayBinding *) { m_indentLevel--; - m_inArrayBinding = false; - m_lastInArrayBinding = nullptr; + m_scope_properties.pop(); addLine("]"); } @@ -986,7 +1142,12 @@ bool DumpAstVisitor::visit(FunctionDeclaration *node) { if (node->isGenerator) head += "*"; - head += " "+node->name+"("+parseFormalParameterList(node->formals)+") {"; + head += " "+node->name+"("+parseFormalParameterList(node->formals)+")"; + + if (node->typeAnnotation != nullptr) + head += " : " + parseType(node->typeAnnotation->type); + + head += " {"; addLine(head); m_indentLevel++; @@ -1000,27 +1161,37 @@ bool DumpAstVisitor::visit(FunctionDeclaration *node) { } bool DumpAstVisitor::visit(UiObjectBinding *node) { - if (m_firstObject) { - if (m_firstOfAll) - m_firstOfAll = false; + if (!scope().m_pendingBinding && scope().m_firstObject) { + if (scope().m_firstOfAll) + scope().m_firstOfAll = false; else addNewLine(); - m_firstObject = false; + scope().m_firstObject = false; } QString name = parseUiQualifiedId(node->qualifiedTypeNameId); QString result = name; + ScopeProperties props; + props.m_bindings = findBindings(node->initializer->members); + m_scope_properties.push(props); + if (node->hasOnToken) result += " on "+parseUiQualifiedId(node->qualifiedId); else result.prepend(parseUiQualifiedId(node->qualifiedId) + ": "); - addNewLine(); - addLine(getComment(node, Comment::Location::Front)); - addLine(result+" {"); + if (scope().m_pendingBinding) { + m_result += result + " {\n"; + + scope().m_pendingBinding = false; + } else { + addNewLine(); + addLine(getComment(node, Comment::Location::Front)); + addLine(result + " {"); + } m_indentLevel++; @@ -1029,6 +1200,8 @@ bool DumpAstVisitor::visit(UiObjectBinding *node) { void DumpAstVisitor::endVisit(UiObjectBinding *node) { m_indentLevel--; + m_scope_properties.pop(); + addLine("}"); addLine(getComment(node, Comment::Location::Back)); @@ -1036,6 +1209,8 @@ void DumpAstVisitor::endVisit(UiObjectBinding *node) { } bool DumpAstVisitor::visit(UiImport *node) { + scope().m_firstOfAll = false; + addLine(getComment(node, Comment::Location::Front)); QString result = "import "; @@ -1060,3 +1235,15 @@ bool DumpAstVisitor::visit(UiImport *node) { return true; } + +bool DumpAstVisitor::visit(UiPragma *node) { + scope().m_firstOfAll = false; + + addLine(getComment(node, Comment::Location::Front)); + QString result = "pragma "+ node->name; + result += getComment(node, Comment::Location::Back_Inline); + + addLine(result); + + return true; +} diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h index 2001f4366e..8c34e01960 100644 --- a/tools/qmlformat/dumpastvisitor.h +++ b/tools/qmlformat/dumpastvisitor.h @@ -32,6 +32,9 @@ #include <QtQml/private/qqmljsastvisitor_p.h> #include <QtQml/private/qqmljsast_p.h> +#include <QHash> +#include <QStack> + #include "commentastvisitor.h" using namespace QQmlJS::AST; @@ -62,11 +65,25 @@ public: bool visit(UiEnumMemberList *node) override; bool visit(UiPublicMember *node) override; bool visit(UiImport *node) override; + bool visit(UiPragma *node) override; void throwRecursionDepthError() override {} bool error() const { return m_error; } private: + struct ScopeProperties { + bool m_firstOfAll = true; + bool m_firstSignal = true; + bool m_firstProperty = true; + bool m_firstBinding = true; + bool m_firstObject = true; + bool m_inArrayBinding = false; + bool m_pendingBinding = false; + + UiObjectMember* m_lastInArrayBinding = nullptr; + QHash<QString, UiObjectMember*> m_bindings; + }; + QString generateIndent() const; QString formatLine(QString line, bool newline = true) const; @@ -106,19 +123,19 @@ private: QString parseFormalParameterList(FormalParameterList *list); + QString parseType(Type *type); + + QString parseFunctionExpression(FunctionExpression *expression, bool omitFunction = false); + + ScopeProperties& scope() { return m_scope_properties.top(); } + 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; + QStack<ScopeProperties> m_scope_properties; - UiObjectMember* m_lastInArrayBinding = nullptr; QString m_result = ""; CommentAstVisitor *m_comment; }; diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp index 036fbe9748..915389e3d2 100644 --- a/tools/qmlformat/main.cpp +++ b/tools/qmlformat/main.cpp @@ -101,11 +101,29 @@ bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImp DumpAstVisitor dump(parser.rootNode(), &comment); - if (dump.error()) { + QString dumpCode = dump.toString(); + + lexer.setCode(dumpCode, 1, true); + + bool dumpSuccess = parser.parse(); + + if (!dumpSuccess) { + if (verbose) { + const auto diagnosticMessages = parser.diagnosticMessages(); + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + qWarning().noquote() << QString::fromLatin1("<formatted>:%2 : %3") + .arg(m.line).arg(m.message); + } + } + + qWarning().noquote() << "Failed to parse formatted code."; + } + + if (dump.error() || !dumpSuccess) { if (force) { qWarning().noquote() << "An error has occurred. The output may not be reliable."; } else { - qWarning().noquote() << "Am error has occurred. Aborting."; + qWarning().noquote() << "An error has occurred. Aborting."; return false; } } @@ -120,10 +138,10 @@ bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImp return false; } - file.write(dump.toString().toUtf8()); + file.write(dumpCode.toUtf8()); file.close(); } else { - QTextStream(stdout) << dump.toString(); + QTextStream(stdout) << dumpCode; } return true; |