From c98719b8cadbf7ca1f1cd5eb101e359cf59f4afc Mon Sep 17 00:00:00 2001 From: Semih Yavuz Date: Thu, 25 May 2023 11:09:26 +0200 Subject: [PATCH] qmldom: Add dom representations for iteration statement Implement visits for while, do-while and foreach node types. Allow creating semantic scope them to find usages/definitions of the script elements within those statements. Add tests for each iteration types implemented. Exclude iterationStatements.qml file from tst_qmlformat test since it includes object destructuring patterns that qmlformat currently doesn't support. Task-number: QTBUG-92876 Task-number: QTBUG-113334 Change-Id: I7936a6b4542c7498e44bdda842a2501cd35b77fa Reviewed-by: Sami Shalayel Reviewed-by: Qt CI Bot --- src/qmldom/qqmldomastcreator.cpp | 98 ++++++ src/qmldom/qqmldomastcreator_p.h | 9 + src/qmldom/qqmldomconstants_p.h | 3 + tests/auto/qml/qmlformat/tst_qmlformat.cpp | 1 + .../domdata/domitem/iterationStatements.qml | 64 ++++ tests/auto/qmldom/domitem/tst_qmldomitem.h | 310 ++++++++++++++++++ 6 files changed, 485 insertions(+) create mode 100644 tests/auto/qmldom/domdata/domitem/iterationStatements.qml diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp index 004e00f9d8..1a9c0e01ce 100644 --- a/src/qmldom/qqmldomastcreator.cpp +++ b/src/qmldom/qqmldomastcreator.cpp @@ -1977,6 +1977,101 @@ void QQmlDomAstCreator::endVisit(AST::SwitchStatement *exp) pushScriptElement(current); } +bool QQmlDomAstCreator::visit(AST::WhileStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::WhileStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptWhileStatement); + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::DoWhileStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::DoWhileStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptDoWhileStatement); + + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + +bool QQmlDomAstCreator::visit(AST::ForEachStatement *) +{ + if (!m_enableScriptExpressions) + return false; + + return true; +} + +void QQmlDomAstCreator::endVisit(AST::ForEachStatement *exp) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(exp, DomType::ScriptForEachStatement); + + if (exp->statement) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::body, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + if (exp->expression) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::expression, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + if (exp->lhs) { + Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); + current->insertChild(Fields::bindingElement, currentScriptNodeEl().takeVariant()); + removeCurrentScriptNode({}); + } + + pushScriptElement(current); +} + static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile) { auto top = qmlFile.top(); @@ -2040,6 +2135,9 @@ QQmlJSASTClassListToVisit switch (topOfStack.kind) { case DomType::ScriptBlockStatement: case DomType::ScriptForStatement: + case DomType::ScriptForEachStatement: + case DomType::ScriptDoWhileStatement: + case DomType::ScriptWhileStatement: case DomType::List: m_domCreator.currentScriptNodeEl().setSemanticScope(scope); break; diff --git a/src/qmldom/qqmldomastcreator_p.h b/src/qmldom/qqmldomastcreator_p.h index 35152eb707..15a154a1cb 100644 --- a/src/qmldom/qqmldomastcreator_p.h +++ b/src/qmldom/qqmldomastcreator_p.h @@ -401,6 +401,15 @@ public: bool visit(AST::SwitchStatement *) override; void endVisit(AST::SwitchStatement *) override; + bool visit(AST::WhileStatement *) override; + void endVisit(AST::WhileStatement *) override; + + bool visit(AST::DoWhileStatement *) override; + void endVisit(AST::DoWhileStatement *) override; + + bool visit(AST::ForEachStatement *) override; + void endVisit(AST::ForEachStatement *) override; + // lists of stuff whose children do not need a qqmljsscope: visitation order can be custom bool visit(AST::ArgumentList *) override; bool visit(AST::UiParameterList *) override; diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h index d360a8a2f7..8ad612bd13 100644 --- a/src/qmldom/qqmldomconstants_p.h +++ b/src/qmldom/qqmldomconstants_p.h @@ -219,6 +219,9 @@ enum class DomType { ScriptCaseClauses, ScriptCaseClause, ScriptDefaultClause, + ScriptWhileStatement, + ScriptDoWhileStatement, + ScriptForEachStatement, ScriptElementStop, // marker to check if a DomType is a scriptelement or not }; diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 1b105451d5..e8eeb9835d 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -155,6 +155,7 @@ void TestQmlformat::initTestCase() // qmlformat cannot handle deconstructing arguments m_ignoreFiles << "tests/auto/qmldom/domdata/domitem/callExpressions.qml"; + m_ignoreFiles << "tests/auto/qmldom/domdata/domitem/iterationStatements.qml"; } QStringList TestQmlformat::findFiles(const QDir &d) diff --git a/tests/auto/qmldom/domdata/domitem/iterationStatements.qml b/tests/auto/qmldom/domdata/domitem/iterationStatements.qml new file mode 100644 index 0000000000..7ad0ec0b10 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/iterationStatements.qml @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +Item { + + function whileStatement() { + const i = 10; + while (i > 0) { + i = i -1; + while ( i > 100) {} + } + + while (i > 0) i = i-1 + } + + function doWhile() { + let a = 7; + do { + const b = a; + a = a - 1; + } while (a > 0) + + do a = a-1; while (a > 0) + } + + function forOf() { + const iterable = [[1,2], [3,4],] + for (var [a,b] of iterable) { + + let t; + for (const [a1, , a2, ...rest] of array) { + + } + for (const k of [1,2,3,4,,,]) { + t += k; + } + for (t of a) { + {} + } + for (t of a) t += k + } + } + + function forIn() { + const enumerable = { + list: [1, 2, 3, 4, 5], + name: 'John', + age: 25 + }; + + for (var [a,b,c,d] in enumerable) { + let t; + for (t in enumerable) { + {} + } + for (const [a1, , a2, ...rest] in enumerable.list) { + + } + for (let t in enumerable) t += k + } + } +} diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.h b/tests/auto/qmldom/domitem/tst_qmldomitem.h index 678c17ca25..b85e6a58dd 100644 --- a/tests/auto/qmldom/domitem/tst_qmldomitem.h +++ b/tests/auto/qmldom/domitem/tst_qmldomitem.h @@ -1932,6 +1932,316 @@ private slots: } } + void iterationStatements() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/iterationStatements.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + + QVERIFY(rootQmlObject); + DomItem methods = rootQmlObject.methods(); + QVERIFY(methods); + + { + // while statement + DomItem whileTest = methods.key("whileStatement") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1); + QVERIFY(whileTest); + QCOMPARE(whileTest.internalKind(), DomType::ScriptWhileStatement); + auto whileScope = whileTest.semanticScope(); + QVERIFY(whileScope); + QVERIFY(*whileScope); + DomItem whileBody = whileTest.field(Fields::body); + QVERIFY(whileBody); + QCOMPARE(whileBody.internalKind(), DomType::ScriptBlockStatement); + auto scope = whileBody.semanticScope(); + QVERIFY(scope); + QVERIFY(*scope); + QCOMPARE(whileBody.field(Fields::statements).index(0).internalKind(), + DomType::ScriptBinaryExpression); // i = i - 1 + QCOMPARE( + whileBody.field(Fields::statements).index(0).field(Fields::left).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(whileBody.field(Fields::statements) + .index(0) + .field(Fields::left) + .field(Fields::identifier) + .value() + .toString(), + u"i"); + DomItem whileExpression = whileTest.field(Fields::expression); + QVERIFY(whileExpression); + QCOMPARE(whileExpression.internalKind(), DomType::ScriptBinaryExpression); // i > 0 + QCOMPARE(whileExpression.field(Fields::left).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(whileExpression.field(Fields::left) + .field(Fields::identifier) + .value() + .toString(), + u"i"); + QCOMPARE(whileExpression.field(Fields::right).internalKind(), DomType::ScriptLiteral); + QCOMPARE(whileExpression.field(Fields::right) + .field(Fields::identifier) + .value() + .toInteger(), + 0); + + DomItem singleLineWhile = methods.key("whileStatement") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(2); + QVERIFY(singleLineWhile); + QCOMPARE(singleLineWhile.internalKind(), DomType::ScriptWhileStatement); + DomItem singleLineWhileBody = singleLineWhile.field(Fields::body); + QVERIFY(singleLineWhileBody); + QCOMPARE(singleLineWhileBody.internalKind(), DomType::ScriptBinaryExpression); + auto singleLineWhileScope = singleLineWhile.semanticScope(); + QVERIFY(singleLineWhileScope); + QVERIFY(*singleLineWhileScope); + QVERIFY(singleLineWhileScope.value()->findJSIdentifier( + "i")); // i is in the parent scope + } + + { + // do-while statement + DomItem doWhile = methods.key("doWhile") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1); + QVERIFY(doWhile); + QCOMPARE(doWhile.internalKind(), DomType::ScriptDoWhileStatement); + + auto doWhileScope = doWhile.semanticScope(); + QVERIFY(doWhileScope); + QVERIFY(*doWhileScope); + DomItem doWhileBody = doWhile.field(Fields::body); + QVERIFY(doWhileBody); + auto doWhileBodyScope = doWhileBody.semanticScope(); + QVERIFY(doWhileBodyScope); + QVERIFY(*doWhileBodyScope); + QVERIFY(doWhileBodyScope.value()->JSIdentifier("b")); // const b = a + QCOMPARE(doWhileBody.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(doWhileBody.field(Fields::statements).index(0).internalKind(), + DomType::ScriptVariableDeclaration); // const b = a + QCOMPARE(doWhileBody.field(Fields::statements).index(1).internalKind(), + DomType::ScriptBinaryExpression); // a = a - 1 + + DomItem doWhileExpression = doWhile.field(Fields::expression); + QVERIFY(doWhileExpression); + QCOMPARE(doWhileExpression.internalKind(), DomType::ScriptBinaryExpression); // a > 0 + QCOMPARE(doWhileExpression.field(Fields::left).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(doWhileExpression.field(Fields::left) + .field(Fields::identifier) + .value() + .toString(), + u"a"); + QCOMPARE(doWhileExpression.field(Fields::right).internalKind(), DomType::ScriptLiteral); + QCOMPARE(doWhileExpression.field(Fields::right) + .field(Fields::identifier) + .value() + .toInteger(), + 0); + DomItem singleDoWhile = methods.key("doWhile") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(2); + auto singleDoWhileScope = singleDoWhile.semanticScope(); + QVERIFY(singleDoWhileScope); + QVERIFY(*singleDoWhileScope); + QVERIFY(singleDoWhileScope.value()->findJSIdentifier("a")); // a = a - 1 + } + + { + // for..of + DomItem statements = methods.key("forOf") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements); + QVERIFY(statements); + QCOMPARE(statements.index(0).internalKind(), DomType::ScriptVariableDeclaration); + DomItem outerForEach = statements.index(1); + QVERIFY(outerForEach); + QCOMPARE(outerForEach.internalKind(), DomType::ScriptForEachStatement); + DomItem bindingElements = + outerForEach.field(Fields::bindingElement).field(Fields::bindingElement); + QVERIFY(bindingElements); + QCOMPARE(bindingElements.internalKind(), DomType::ScriptArray); + QCOMPARE(bindingElements.field(Fields::elements).length(), 2); + QCOMPARE(bindingElements.field(Fields::elements) + .index(1) + .field(Fields::identifier) + .value() + .toString(), + "b"); + QCOMPARE(outerForEach.field(Fields::expression).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(outerForEach.field(Fields::expression) + .field(Fields::identifier) + .value() + .toString(), + u"iterable"); + DomItem forEachStatements = outerForEach.field(Fields::body).field(Fields::statements); + QCOMPARE(forEachStatements.index(0).internalKind(), DomType::ScriptVariableDeclaration); + QCOMPARE(forEachStatements.index(1).internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(forEachStatements.index(2).internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(forEachStatements.index(3).internalKind(), DomType::ScriptForEachStatement); + DomItem firstForEach = forEachStatements.index(1); + QVERIFY(firstForEach); + DomItem bindingElement = + firstForEach.field(Fields::bindingElement).field(Fields::bindingElement); + QCOMPARE(bindingElement.internalKind(), DomType::ScriptArray); + + QCOMPARE(bindingElement.field(Fields::elements).length(), 4); + QCOMPARE(bindingElement.field(Fields::elements) + .index(0) + .field(Fields::identifier) + .field(Fields::identifier) + .value() + .toString(), + "a1"); + DomItem secondForEach = forEachStatements.index(2); + QCOMPARE(secondForEach.field(Fields::bindingElement).internalKind(), + DomType::ScriptPattern); + QCOMPARE(secondForEach.field(Fields::bindingElement) + .field(Fields::identifier) + .field(Fields::identifier) + .value() + .toString(), + "k"); + QCOMPARE(secondForEach.internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(secondForEach.field(Fields::expression).internalKind(), DomType::ScriptArray); + QVERIFY(secondForEach.field(Fields::body)); + QCOMPARE(secondForEach.field(Fields::body).internalKind(), + DomType::ScriptBlockStatement); + DomItem thirdForEach = forEachStatements.index(3); + QCOMPARE(thirdForEach.field(Fields::bindingElement).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(thirdForEach.field(Fields::bindingElement).value().toString(), "t"); + + QCOMPARE(thirdForEach.internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(thirdForEach.field(Fields::expression).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(thirdForEach.field(Fields::expression) + .field(Fields::identifier) + .value() + .toString(), + u"a"); + QVERIFY(thirdForEach.field(Fields::body)); + QCOMPARE(thirdForEach.field(Fields::body).internalKind(), + DomType::ScriptBlockStatement); + + DomItem forthForEach = forEachStatements.index(3); + QVERIFY(forthForEach); + auto forthForEachScope = forthForEach.semanticScope(); + QVERIFY(forthForEachScope); + QVERIFY(*forthForEachScope); + QVERIFY(forthForEachScope.value()->findJSIdentifier("t")); // t lives in parent scope + } + + { + // for..in + DomItem statements = methods.key("forIn") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements); + QVERIFY(statements); + QCOMPARE(statements.index(0).internalKind(), DomType::ScriptVariableDeclaration); + DomItem outerForEach = statements.index(1); + QVERIFY(outerForEach); + auto outerForEachScope = outerForEach.semanticScope(); + QVERIFY(outerForEachScope); + QVERIFY(*outerForEachScope); + QVERIFY(outerForEachScope.value()->findJSIdentifier("a")); // var [a,b,c,d] + QVERIFY(outerForEachScope.value()->findJSIdentifier("b")); + QVERIFY(outerForEachScope.value()->findJSIdentifier("c")); + QVERIFY(outerForEachScope.value()->findJSIdentifier("d")); + QCOMPARE(outerForEach.internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(statements.index(1).internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(outerForEach.field(Fields::expression) + .field(Fields::identifier) + .value() + .toString(), + "enumerable"); + auto outerForEachBodyScope = outerForEach.field(Fields::body).semanticScope(); + QVERIFY(outerForEachBodyScope); + QVERIFY(*outerForEachBodyScope); + QVERIFY(outerForEachBodyScope.value()->JSIdentifier("t")); // let t + DomItem forInStatements = outerForEach.field(Fields::body).field(Fields::statements); + QCOMPARE(forInStatements.index(0).internalKind(), DomType::ScriptVariableDeclaration); + QCOMPARE(forInStatements.index(1).internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(forInStatements.index(2).internalKind(), DomType::ScriptForEachStatement); + DomItem firstForEach = forInStatements.index(1); + QVERIFY(firstForEach); + auto firstForEachScope = firstForEach.semanticScope(); + QVERIFY(firstForEachScope); + QVERIFY(*firstForEachScope); + QVERIFY(firstForEachScope.value()->findJSIdentifier("t")); + QCOMPARE(firstForEach.field(Fields::bindingElement).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(firstForEach.field(Fields::bindingElement) + .field(Fields::identifier) + .value() + .toString(), + "t"); + QCOMPARE(firstForEach.internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(firstForEach.field(Fields::expression).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(firstForEach.field(Fields::expression) + .field(Fields::identifier) + .value() + .toString(), + "enumerable"); + QVERIFY(firstForEach.field(Fields::body)); + QCOMPARE(firstForEach.field(Fields::body).internalKind(), + DomType::ScriptBlockStatement); + + DomItem secondForEach = forInStatements.index(2); + QVERIFY(secondForEach); + auto secondForEachScope = secondForEach.semanticScope(); + QVERIFY(secondForEachScope); + QVERIFY(*secondForEachScope); + QVERIFY(secondForEachScope.value()->JSIdentifier("a1")); // const [a1,,a2,...rest] + QVERIFY(secondForEachScope.value()->JSIdentifier("a2")); + QVERIFY(secondForEachScope.value()->JSIdentifier("rest")); + + DomItem bindingElement = + secondForEach.field(Fields::bindingElement).field(Fields::bindingElement); + QCOMPARE(bindingElement.internalKind(), DomType::ScriptArray); + QCOMPARE(bindingElement.field(Fields::elements) + .index(3) + .field(Fields::identifier) + .field(Fields::identifier) + .value() + .toString(), + "rest"); + QCOMPARE(secondForEach.internalKind(), DomType::ScriptForEachStatement); + QCOMPARE(secondForEach.field(Fields::expression).internalKind(), + DomType::ScriptBinaryExpression); + QVERIFY(secondForEach.field(Fields::body)); + QCOMPARE(secondForEach.field(Fields::body).internalKind(), + DomType::ScriptBlockStatement); + DomItem thirdForEach = forInStatements.index(3); + QVERIFY(thirdForEach); + auto thirdForEachScope = thirdForEach.semanticScope(); + QVERIFY(thirdForEachScope); + QVERIFY(*thirdForEachScope); + QVERIFY(thirdForEachScope.value()->JSIdentifier("t")); + } + } + private: struct DomItemWithLocation {