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 <sami.shalayel@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Semih Yavuz 2023-05-25 11:09:26 +02:00
parent 955a162a54
commit c98719b8ca
6 changed files with 485 additions and 0 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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
};

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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
{