diff options
Diffstat (limited to 'tests/auto/qmldom')
92 files changed, 4513 insertions, 781 deletions
diff --git a/tests/auto/qmldom/CMakeLists.txt b/tests/auto/qmldom/CMakeLists.txt index 7e2a0b885f..f3186e2263 100644 --- a/tests/auto/qmldom/CMakeLists.txt +++ b/tests/auto/qmldom/CMakeLists.txt @@ -10,4 +10,3 @@ add_subdirectory(stringdumper) add_subdirectory(merging) add_subdirectory(reformatter) add_subdirectory(combined) -add_subdirectory(standalone) diff --git a/tests/auto/qmldom/combined/CMakeLists.txt b/tests/auto/qmldom/combined/CMakeLists.txt index 6903f89eff..add44acf0b 100644 --- a/tests/auto/qmldom/combined/CMakeLists.txt +++ b/tests/auto/qmldom/combined/CMakeLists.txt @@ -5,6 +5,13 @@ ## tst_dom_all Binary executing all tests together ## (simpler to verify coverage) ##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_dom_all LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. @@ -20,7 +27,6 @@ qt_internal_add_test(tst_dom_all ../domitem/tst_qmldomitem.h ../merging/tst_dommerging.h ../reformatter/tst_reformatter.h - ../standalone/tst_standalone.h INCLUDE_DIRECTORIES .. DEFINES diff --git a/tests/auto/qmldom/combined/tst_dom_all.cpp b/tests/auto/qmldom/combined/tst_dom_all.cpp index b3920f8e41..6e3e0bd851 100644 --- a/tests/auto/qmldom/combined/tst_dom_all.cpp +++ b/tests/auto/qmldom/combined/tst_dom_all.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "stringdumper/tst_qmldomstringdumper.h" #include "errormessage/tst_qmldomerrormessage.h" @@ -7,7 +7,6 @@ #include "merging/tst_dommerging.h" #include "path/tst_qmldompath.h" #include "reformatter/tst_reformatter.h" -#include "standalone/tst_standalone.h" #include <QtCore/qdebug.h> @@ -38,10 +37,6 @@ int main(int argc, char *argv[]) QQmlJS::Dom::TestReformatter test; status |= QTest::qExec(&test, argc, argv); } - { - QQmlJS::Dom::TestStandalone test; - status |= QTest::qExec(&test, argc, argv); - } if (status) qWarning() << "Combined test failed!"; return status; diff --git a/tests/auto/qmldom/domdata/domitem/Base.qml b/tests/auto/qmldom/domdata/domitem/Base.qml new file mode 100644 index 0000000000..919619ffe6 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/Base.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int a +} diff --git a/tests/auto/qmldom/domdata/domitem/Derived.qml b/tests/auto/qmldom/domdata/domitem/Derived.qml new file mode 100644 index 0000000000..8a6ce158c3 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/Derived.qml @@ -0,0 +1,5 @@ +import QtQuick + +Base { + property int b +} diff --git a/tests/auto/qmldom/domdata/domitem/ImportMeImplicitly.ui.qml b/tests/auto/qmldom/domdata/domitem/ImportMeImplicitly.ui.qml new file mode 100644 index 0000000000..169d162469 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/ImportMeImplicitly.ui.qml @@ -0,0 +1,6 @@ +import QtQuick + +Item { + property int helloProperty + +} diff --git a/tests/auto/qmldom/domdata/domitem/WithImplicitImport.qml b/tests/auto/qmldom/domdata/domitem/WithImplicitImport.qml new file mode 100644 index 0000000000..5560aee727 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/WithImplicitImport.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/tests/auto/qmldom/domdata/domitem/Yyy.qml b/tests/auto/qmldom/domdata/domitem/Yyy.qml new file mode 100644 index 0000000000..b2235ff9cc --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/Yyy.qml @@ -0,0 +1,35 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick 2.0 +import QtQuick as QQ + +Zzz { + id: root + width: height + Rectangle { + color: "green" + anchors.fill: parent + height: root.foo.height + width: root.height + + } + + function lala() {} + property Rectangle foo: Rectangle{ height: 200 } + function longfunction(a, b, c = "c", d = "d"): string { + return "hehe: " + c + d + } + + // documentedFunction: is documented + // returns 'Good' + function documentedFunction(arg1, arg2 = "Qt"): string { + return "Good" + } + QQ.Rectangle { + color:"red" + } + + component IC: Zzz { property SomeBase data } + property SomeBase mySomeBase +} diff --git a/tests/auto/qmldom/domdata/domitem/aliasProperties.qml b/tests/auto/qmldom/domdata/domitem/aliasProperties.qml index c8653e3446..e2521b7afe 100644 --- a/tests/auto/qmldom/domdata/domitem/aliasProperties.qml +++ b/tests/auto/qmldom/domdata/domitem/aliasProperties.qml @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick Item { diff --git a/tests/auto/qmldom/domdata/domitem/astComments.qml b/tests/auto/qmldom/domdata/domitem/astComments.qml new file mode 100644 index 0000000000..6fca4c69e5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/astComments.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + function ninja() { + // tst comment + const patron = 34; +/*Ast Comment*/ const ppp = 23; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/attachedOrGroupedProperties.qml b/tests/auto/qmldom/domdata/domitem/attachedOrGroupedProperties.qml new file mode 100644 index 0000000000..ff7720506c --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/attachedOrGroupedProperties.qml @@ -0,0 +1,34 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Text { + id: grouped + + // Dot notation + Binding { + grouped.font.family: "mono" + } + + // Group notation + Test { + id: test + myText { + font { + pixelSize: 12 + } + } + } + + component Test : Rectangle { + property Text myText: text1 + Text { + id: text1 + } + } + + Keys.onPressed: (event)=> { + + } +} diff --git a/tests/auto/qmldom/domdata/domitem/breakStatement.qml b/tests/auto/qmldom/domdata/domitem/breakStatement.qml new file mode 100644 index 0000000000..212fe095c0 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/breakStatement.qml @@ -0,0 +1,9 @@ +import QtQuick + +Item { + function f() { + break helloWorld; + break; + } + +} diff --git a/tests/auto/qmldom/domdata/domitem/callExpressions.qml b/tests/auto/qmldom/domdata/domitem/callExpressions.qml new file mode 100644 index 0000000000..d0de350fae --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/callExpressions.qml @@ -0,0 +1,61 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + property var p: f() + // crash if arguments are wrongly collected, e.g. because they are stolen from other unimplemented scriptelements + property var p2: f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) + + // dummy test: if these ones fail, then the deconstructing parameters cannot possibly work (they share some + // common code). + function deconstruct() { + let { a } = { a: 32, b: 42}, { b, c } = { b: 32, c: 42}, [ d, e, f ] = [ 111, 222, 333 ]; + let x = [,,,,[1,2,3],,]; + } + + function f(q,w,e,r,t,y) { + let helloF = 32 + return 42 + } + + function fWithDefault(q = 1, w = 2, e, r = 4, t, y = 6) { + let helloFWithDefault = {} + return 42 + } + + function marmelade(...onTheBread) { + let helloMarmelade = 123 + return 42 + } + + function marmelade2(spread, it,...onTheBread) { + let helloMarmelade2 = 123 + return 42 + } + + // check if nothing crashes for empty stuff + function empty({}, []) { + let {} = {}; + let [] = []; + } + + component MyType: Item{} + + function withTypes(a: int, b: MyType) {} + function empty() {} + signal mySignal() + + + property var p3: evil({ hello: "World", y: "yyy"}, [1,2,3], { is: {a: 111, lot: 222, of: 333, fun: 444, really: ["!",]}}) + + function evil({ hello = "world", x = 42 }, + [n = 42, m = 43, o = 44], + { destructuring, is = {a, lot, of}, fun = 42 } = {destructuring : 123, is : {x : 123}, fun : 456}) { + const helloEvil = "asdf" + return 42 + } + + +} diff --git a/tests/auto/qmldom/domdata/domitem/checkScopes.qml b/tests/auto/qmldom/domdata/domitem/checkScopes.qml new file mode 100644 index 0000000000..39903e9d90 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/checkScopes.qml @@ -0,0 +1,11 @@ +import QtQuick + +Item { + id: root + property int myInt + property int myInt2 + + myInt: 42 + myInt2: 123 + +} diff --git a/tests/auto/qmldom/domdata/domitem/commaExpression.qml b/tests/auto/qmldom/domdata/domitem/commaExpression.qml new file mode 100644 index 0000000000..c6576b5dad --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/commaExpression.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { + function f(a, b, c) { + a, b, c; + } + +} diff --git a/tests/auto/qmldom/domdata/domitem/conditionalExpression.qml b/tests/auto/qmldom/domdata/domitem/conditionalExpression.qml new file mode 100644 index 0000000000..027078b0ef --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/conditionalExpression.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { + function f(a, b, c) { + a?b:c; + } + +} diff --git a/tests/auto/qmldom/domdata/domitem/continueStatement.qml b/tests/auto/qmldom/domdata/domitem/continueStatement.qml new file mode 100644 index 0000000000..735ded4bda --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/continueStatement.qml @@ -0,0 +1,9 @@ +import QtQuick + +Item { + function f() { + continue helloWorld; + continue; + } + +} diff --git a/tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml b/tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml new file mode 100644 index 0000000000..bef28ffc45 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/crashes/bracketsInBinding.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property int foo: {} +} diff --git a/tests/auto/qmldom/domdata/domitem/crashes/lambda.qml b/tests/auto/qmldom/domdata/domitem/crashes/lambda.qml new file mode 100644 index 0000000000..cab0fec143 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/crashes/lambda.qml @@ -0,0 +1,7 @@ +import QtQuick.Controls + +Action { + onTriggered: foo(Bla.Bar, function() { + console.log("Hello") + }) +} diff --git a/tests/auto/qmldom/domdata/domitem/crashes/templateStrings.qml b/tests/auto/qmldom/domdata/domitem/crashes/templateStrings.qml new file mode 100644 index 0000000000..feb5646496 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/crashes/templateStrings.qml @@ -0,0 +1,10 @@ +import QtQuick + + +Item { + property string verbatim1: 'A "verbatim" string!' + property string verbatim2: "A 'verbatim' string\u2757" + property string verbatim3: `400 + 300 is ${400 + 300}. + +mutliline` +} diff --git a/tests/auto/qmldom/domdata/domitem/ecmaScriptClass.qml b/tests/auto/qmldom/domdata/domitem/ecmaScriptClass.qml new file mode 100644 index 0000000000..e86ea737e5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/ecmaScriptClass.qml @@ -0,0 +1,44 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + +function f() { + +var count = 0; + +class Person { + constructor(name) { + this._name = name + } +} + +class Employee extends Person{ + + + constructor(name, age) { + super(name); + this._name = name; + this._age = age; + ++count; + } + + get /* do we get the comment? */ name() { + return this._name.toUpperCase(); + } + + set name(newName){ + if (newName) { + this._name = newName; + } + } + + static get count() { return count;} +} + + +} + +} diff --git a/tests/auto/qmldom/domdata/domitem/emptyMethodBody.qml b/tests/auto/qmldom/domdata/domitem/emptyMethodBody.qml new file mode 100644 index 0000000000..d987d3e649 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/emptyMethodBody.qml @@ -0,0 +1,6 @@ +import QtQuick + +Item { + function f(x) {} + +} diff --git a/tests/auto/qmldom/domdata/domitem/enumDeclarations.qml b/tests/auto/qmldom/domdata/domitem/enumDeclarations.qml new file mode 100644 index 0000000000..260f079fd6 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/enumDeclarations.qml @@ -0,0 +1,7 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +Item { + enum Cats { Patron, Mafya, Kivrik = -1 } +} diff --git a/tests/auto/qmldom/domdata/domitem/fieldMemberExpression.qml b/tests/auto/qmldom/domdata/domitem/fieldMemberExpression.qml new file mode 100644 index 0000000000..8890a0c8cb --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fieldMemberExpression.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + id: root + + property int p: 42 + + property Item property1: Item { + property bool p: 44 + property Item property2: Item { + property string p: "Hello World" + property Item property3: Item { + property real p: 123 + } + } + } + + property var p1: p + property var p1Qualified: root.p + property var p1Bracket: root["p"] + property var p1Index: root[42] + property var p1Key: root[p] + + property var p2: property1.p + property var p2Qualified: root.property1.p + + property var p3: property1.property2.p + property var p3Qualified: root.property1.property2.p +} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegion.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegion.qml new file mode 100644 index 0000000000..f441efb5e2 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegion.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { + property int helloWorld + + // before helloWorld binding + helloWorld: 42 // after helloWorld binding +} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml new file mode 100644 index 0000000000..a69505c544 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/comments.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +/* +splitting +multiline +*/ +// single line +/* another comment */ +QtObject {} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegions/enums.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/enums.qml new file mode 100644 index 0000000000..e6e0f7a205 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/enums.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + enum OSC { + sin = 1, + saw, + tri = 16 + } +} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegions/functions.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/functions.qml new file mode 100644 index 0000000000..e045e5a4cc --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/functions.qml @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + function a() : int {} + + component A : Item { + function b(k: int) : int {} + } + + signal k(int a) + signal kk(a: int) +} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegions/imports.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/imports.qml new file mode 100644 index 0000000000..ccf012bc63 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/imports.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQml 2.15 +import QtQuick.Controls as Patron +import "../cats" + +Item { + +} diff --git a/tests/auto/qmldom/domdata/domitem/fileLocationRegions/pragmas.qml b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/pragmas.qml new file mode 100644 index 0000000000..5f34f9ae33 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/fileLocationRegions/pragmas.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +pragma Singleton +pragma ComponentBehavior: Bound +pragma FunctionSignatureBehavior: Enforced +pragma ValueTypeBehavior: Copy,Addressable + +import QtQml + +QtObject {} diff --git a/tests/auto/qmldom/domdata/domitem/finalizeScriptExpressions.qml b/tests/auto/qmldom/domdata/domitem/finalizeScriptExpressions.qml new file mode 100644 index 0000000000..5a0c811396 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/finalizeScriptExpressions.qml @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + property var binding + binding: 42 + + property var bindingInPropertyDefinition: 123 + + function return42(aa: Item = 33, bb: string = "Hello", cc = binding): int { + return 42 + } + function empty(aa: Item, bb: string, cc) {} + function full(aa: Item, bb: string, cc) + { + const x = 42; + const formula = (x + 5) * 2 + 10 - x + return formula; + } + + id: idBinding + + property var arrayBinding + arrayBinding:[] + property var objectBinding + objectBinding: Item {} +} diff --git a/tests/auto/qmldom/domdata/domitem/forStatements.qml b/tests/auto/qmldom/domdata/domitem/forStatements.qml new file mode 100644 index 0000000000..6c06098309 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/forStatements.qml @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + function f() { + let sum = 0, helloWorld = "hello" + for (let i = 0; i < 100; i = i + 1) { + sum = sum + 1 + for (;;) + i = 42 + } + } +} diff --git a/tests/auto/qmldom/domdata/domitem/ifStatements.qml b/tests/auto/qmldom/domdata/domitem/ifStatements.qml new file mode 100644 index 0000000000..90d1bd3ac8 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/ifStatements.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + function conditional() { + let i = 5 + if (i) + i = 42 + + if (i == 55) + i = 32 + else + i = i - 1 + + if (i == 42) { + i = 111 + } + + if (i == 746) { + i = 123 + } else { + i = 456 + } + } +} diff --git a/tests/auto/qmldom/domdata/domitem/import.js b/tests/auto/qmldom/domdata/domitem/import.js new file mode 100644 index 0000000000..e13db4c1a4 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/import.js @@ -0,0 +1,2 @@ +.import "main.js" as Main +console.log(Main.a); diff --git a/tests/auto/qmldom/domdata/domitem/inactiveVisitorMarkerCrash.qml b/tests/auto/qmldom/domdata/domitem/inactiveVisitorMarkerCrash.qml new file mode 100644 index 0000000000..c706251e47 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/inactiveVisitorMarkerCrash.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Window { + width: 640 + height: 480 + visible: true + title: qsTr("Hello World") + + HelloWorld { myP: 55; myPPP: 55 } +} diff --git a/tests/auto/qmldom/domdata/domitem/inlineComponents.qml b/tests/auto/qmldom/domdata/domitem/inlineComponents.qml new file mode 100644 index 0000000000..5ae0af4eb1 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/inlineComponents.qml @@ -0,0 +1,17 @@ +import QtQuick + +Item { + id: mainComponent + + component IC1: Item { property string firstIC } + component IC2: Item { property string secondIC } + + Item { + Item { + Item { + component IC3: Item { property string thirdIC } + } + } + } + +} diff --git a/tests/auto/qmldom/domdata/domitem/inlineObject.qml b/tests/auto/qmldom/domdata/domitem/inlineObject.qml new file mode 100644 index 0000000000..2f5941f4da --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/inlineObject.qml @@ -0,0 +1,7 @@ +import QtQuick + +Item { + component IC: Item {} + property var myItem: Item {} + property var myItem2: IC {} +} diff --git a/tests/auto/qmldom/domdata/domitem/invalidAliasProperties.qml b/tests/auto/qmldom/domdata/domitem/invalidAliasProperties.qml index 266e4139b7..7998642ef5 100644 --- a/tests/auto/qmldom/domdata/domitem/invalidAliasProperties.qml +++ b/tests/auto/qmldom/domdata/domitem/invalidAliasProperties.qml @@ -1,5 +1,5 @@ // Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick Item { diff --git a/tests/auto/qmldom/domdata/domitem/iterationStatements.qml b/tests/auto/qmldom/domdata/domitem/iterationStatements.qml new file mode 100644 index 0000000000..e75eeb2d7d --- /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 GPL-3.0-only + +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/domdata/domitem/lambdas.qml b/tests/auto/qmldom/domdata/domitem/lambdas.qml new file mode 100644 index 0000000000..c241bb77ae --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/lambdas.qml @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + signal helloSignal + + function method() { + console.log("helloMethod"); + let myLambda = function(a, b) { return a + b }; + let myArrow = (v, w) => a + b; + } + + onHelloSignal: function(x, y, z) { console.log("HelloLambda"); } + + function testNestedFunctions() { + function nested(tic, tac, toe) { return tic + tac/3 + toe/2} + nested() + } + + function generators() { + function *myGeneratorDeclaration(a, b) { yield 5 }; + let myGenerator = function*(tic, tac, toe) { yield tic + tac - toe }; + } + + function *generatorInQmlObject() { + function nested(q,w,e,r) { return q + w + e - r; } + function *nested2(a,z,e,r) { yield a + z + e - r; yield 42; } + yield 4; + yield* nested2(1,2,3,4); + const t = (function (a) { + return a + 100; + }); + } + function traditionalLambda() { + const tradition = (function (a) { + return a + 100; + }); + } +} diff --git a/tests/auto/qmldom/domdata/domitem/module.mjs b/tests/auto/qmldom/domdata/domitem/module.mjs new file mode 100644 index 0000000000..6838766329 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/module.mjs @@ -0,0 +1,6 @@ + +import { helper } from "utils.mjs" + +export function entry() { + return helper() +} diff --git a/tests/auto/qmldom/domdata/domitem/nullStatements.qml b/tests/auto/qmldom/domdata/domitem/nullStatements.qml new file mode 100644 index 0000000000..f4be9b30de --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/nullStatements.qml @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + function testForNull() { + for (;;) + {} + for (;;) + x + {} {} {} {} + } +} diff --git a/tests/auto/qmldom/domdata/domitem/objectBindings.qml b/tests/auto/qmldom/domdata/domitem/objectBindings.qml new file mode 100644 index 0000000000..c02a7113a5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/objectBindings.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick as QQ + +Item { + id: root + Item {} + QQ.Item {} + property var x: root. + QQ.Drag {} +} diff --git a/tests/auto/qmldom/domdata/domitem/propertyBindings.qml b/tests/auto/qmldom/domdata/domitem/propertyBindings.qml new file mode 100644 index 0000000000..3c2931cb5d --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/propertyBindings.qml @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + property int a: 42 + property int b + + b: a + + id: root +} diff --git a/tests/auto/qmldom/domdata/domitem/returnStatements.qml b/tests/auto/qmldom/domdata/domitem/returnStatements.qml new file mode 100644 index 0000000000..4f503244fb --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/returnStatements.qml @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + // TODO: Add a test for returning void + function returningFunction(i) { + if (i) + return 123; + else + return 1 + 2; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/simplestJSStatement.js b/tests/auto/qmldom/domdata/domitem/simplestJSStatement.js new file mode 100644 index 0000000000..d954a87fc0 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/simplestJSStatement.js @@ -0,0 +1 @@ +let v=1; diff --git a/tests/auto/qmldom/domdata/domitem/simplestJSmodule.mjs b/tests/auto/qmldom/domdata/domitem/simplestJSmodule.mjs new file mode 100644 index 0000000000..401b25314b --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/simplestJSmodule.mjs @@ -0,0 +1 @@ +export function entry() {} diff --git a/tests/auto/qmldom/domdata/domitem/switchStatement.qml b/tests/auto/qmldom/domdata/domitem/switchStatement.qml new file mode 100644 index 0000000000..b2bacec8c0 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/switchStatement.qml @@ -0,0 +1,28 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + function switchStatement(){ + const animals = "cat"; + const no = 0; + switch (animals) { + case "cat": + switch (no) { + case 0: return "patron"; + case 1: return "mafik"; + default: return "none"; + } + case "dog": { + // check if qqmljsscope is created for this case + const name = "tekila"; + let another = "mutantx"; + return name; + } + default: return "monster"; + case "moreCases!": + return "moreCaseClauses?" + } + } +} diff --git a/tests/auto/qmldom/domdata/domitem/tryStatements.qml b/tests/auto/qmldom/domdata/domitem/tryStatements.qml new file mode 100644 index 0000000000..5bd29db058 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/tryStatements.qml @@ -0,0 +1,10 @@ +import QtQuick + +Item { + function f() { + try { insideTry; } catch(catchExpression) { insideCatch; } finally { insideFinally; } + try { insideTry; } catch(catchExpression) { insideCatch; } + try { insideTry; } finally { insideFinally; } + } + +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/decrement.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/decrement.qml new file mode 100644 index 0000000000..fb9fe6f252 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/decrement.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + --a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/delete.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/delete.qml new file mode 100644 index 0000000000..39063b8c2f --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/delete.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + delete a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/increment.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/increment.qml new file mode 100644 index 0000000000..e7acd3dfe5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/increment.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + ++a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/not.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/not.qml new file mode 100644 index 0000000000..6c3a1aa035 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/not.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + !a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/postDecrement.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/postDecrement.qml new file mode 100644 index 0000000000..52d170fae0 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/postDecrement.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + a--; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/postIncrement.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/postIncrement.qml new file mode 100644 index 0000000000..30b4da8c8f --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/postIncrement.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + a++; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/tilde.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/tilde.qml new file mode 100644 index 0000000000..b35a00bd79 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/tilde.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + ~a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/typeof.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/typeof.qml new file mode 100644 index 0000000000..1696c5bd27 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/typeof.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + typeof a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/unaryMinus.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/unaryMinus.qml new file mode 100644 index 0000000000..e711c4d2f5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/unaryMinus.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + -a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/unaryPlus.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/unaryPlus.qml new file mode 100644 index 0000000000..1b9ecbf5b5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/unaryPlus.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + +a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/unaryExpressions/void.qml b/tests/auto/qmldom/domdata/domitem/unaryExpressions/void.qml new file mode 100644 index 0000000000..d9396e1b39 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/unaryExpressions/void.qml @@ -0,0 +1,7 @@ +import QtQuick 2.15 + +Item { + function f(a) { + void a; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/variableDeclarations.qml b/tests/auto/qmldom/domdata/domitem/variableDeclarations.qml new file mode 100644 index 0000000000..7d5d2ac9a5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/variableDeclarations.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + function count() { + let one = 1, two = 2, three = 3, four = 4, five = 5, six = 6 + let testMe = 123 + } + // for now only numeric literal and string literal is supported + function f() { + let sum = 0, helloWorld = "hello" + const a = 3; + const b = "patron"; + var aa = helloWorld, bb = aa; + + const bool1 = true; + let bool2 = false; + var nullVar = null; + return sum; + } +} diff --git a/tests/auto/qmldom/domdata/domitem/visitTreeFilter.qml b/tests/auto/qmldom/domdata/domitem/visitTreeFilter.qml new file mode 100644 index 0000000000..5d0b06f91f --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/visitTreeFilter.qml @@ -0,0 +1,4 @@ + +QtObject { + property int helloProperty +} diff --git a/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml index 0911f0e102..ab722f94be 100644 --- a/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml +++ b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml @@ -26,8 +26,10 @@ Item { // before zz zz - // after z + zz /*before (a b)*/(/* before a */ a * /* after a */ b * /*after b*/ c); // after (a * b * c) + if (y == 6) // if comment console.log("pippo"); + a + b; // comment // footer diff --git a/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml index 522cbbc777..c6e33f9389 100644 --- a/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml +++ b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml @@ -26,8 +26,10 @@ Item { // before zz zz - // after z + zz /*before (a b)*/(/* before a */ a * /* after a */ b * /*after b*/ c); // after (a * b * c) + if (y == 6) // if comment console.log("pippo"); + a + b; // comment // footer diff --git a/tests/auto/qmldom/domdata/reformatter/file1.qml b/tests/auto/qmldom/domdata/reformatter/file1.qml index 36819b9998..e0382bf57c 100644 --- a/tests/auto/qmldom/domdata/reformatter/file1.qml +++ b/tests/auto/qmldom/domdata/reformatter/file1.qml @@ -14,6 +14,8 @@ Window { Rectangle { anchors.fill: parent + Behavior on opacity {} + ListView { width: parent.width model: { diff --git a/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml b/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml index 361d054f9c..6a24f907d1 100644 --- a/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml +++ b/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml @@ -14,6 +14,9 @@ Window { Rectangle { anchors.fill: parent + Behavior on opacity { + } + ListView { model: { MySingleton.mySignal(); @@ -38,7 +41,7 @@ Window { function f(v = 4) { let c = 0; return { - "a": function () { + a: function () { if (b == 0) c += 78 * 5 * v; }() diff --git a/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml b/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml index 18c719020c..2e7483f453 100644 --- a/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml +++ b/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml @@ -14,6 +14,8 @@ Window { Rectangle { anchors.fill: parent + Behavior on opacity {} + ListView { width: parent.width model: { @@ -32,7 +34,7 @@ Window { function f(v = 4) { let c = 0; return { - "a": function () { + a: function () { if (b == 0) c += 78 * 5 * v; }() diff --git a/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml b/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml index 224f1c1dce..4085b91e6e 100644 --- a/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml +++ b/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml @@ -14,6 +14,8 @@ property var arrTrailingComma: [1,2,3,] Rectangle { anchors.fill: parent +Behavior on opacity {} + ListView { width: parent.width model: { diff --git a/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml b/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml index 48a961930d..3ed3aa208c 100644 --- a/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml +++ b/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml @@ -52,7 +52,7 @@ Window { function f(v) { let c = 0; return { - "a": function () { + a: function () { if (b == 0) c += 78 * 5 * v; }() diff --git a/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml b/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml index cb84168307..7473283605 100644 --- a/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml +++ b/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml @@ -8,7 +8,7 @@ Item { function foo() { theItem.foo("The issue is exacerbated if the object literal is wrapped onto the next line like so:", { - "foo": theFoo - }); + "foo": theFoo + }); } } diff --git a/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml b/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml index b01eca88d3..fde8ffd686 100644 --- a/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml +++ b/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml @@ -4,7 +4,7 @@ Item { function foo() { iterableObj = [1, 2]; obj = { - "a": 42 + a: 42 }; let x = (console.log("bla\n"), 3); myFunction(...iterableObj); // pass all elements of iterableObj as arguments to function myFunction diff --git a/tests/auto/qmldom/domitem/CMakeLists.txt b/tests/auto/qmldom/domitem/CMakeLists.txt index bf12ad866c..e6f8117902 100644 --- a/tests/auto/qmldom/domitem/CMakeLists.txt +++ b/tests/auto/qmldom/domitem/CMakeLists.txt @@ -6,6 +6,13 @@ ##################################################################### ## tst_qmldomitem Test: ##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmldomitem LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.cpp b/tests/auto/qmldom/domitem/tst_qmldomitem.cpp index ea450e6969..97b7dcc3ed 100644 --- a/tests/auto/qmldom/domitem/tst_qmldomitem.cpp +++ b/tests/auto/qmldom/domitem/tst_qmldomitem.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_qmldomitem.h" QTEST_MAIN(QQmlJS::Dom::TestDomItem) diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.h b/tests/auto/qmldom/domitem/tst_qmldomitem.h index 433a92a2b2..6c74b7f746 100644 --- a/tests/auto/qmldom/domitem/tst_qmldomitem.h +++ b/tests/auto/qmldom/domitem/tst_qmldomitem.h @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TST_QMLDOMITEM_H #define TST_QMLDOMITEM_H @@ -9,6 +9,7 @@ #include <QtQmlDom/private/qqmldommock_p.h> #include <QtQmlDom/private/qqmldomcompare_p.h> #include <QtQmlDom/private/qqmldomfieldfilter_p.h> +#include <QtQmlDom/private/qqmldomscriptelements_p.h> #include <QtTest/QtTest> #include <QtCore/QCborValue> @@ -16,14 +17,18 @@ #include <QtCore/QLibraryInfo> #include <QtCore/QFileInfo> +#include <deque> #include <memory> +#include <utility> +#include <variant> +#include <vector> QT_BEGIN_NAMESPACE namespace QQmlJS { namespace Dom { -inline DomItem wrapInt(DomItem &self, const PathEls::PathComponent &p, const int &i) +inline DomItem wrapInt(const DomItem &self, const PathEls::PathComponent &p, const int &i) { return self.subDataItem(p, i); } @@ -51,8 +56,9 @@ private slots: qmltypeDirs = QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::QmlImportsPath) }); universePtr = std::shared_ptr<DomUniverse>(new DomUniverse(QStringLiteral(u"dummyUniverse"))); - envPtr = std::shared_ptr<DomEnvironment>(new DomEnvironment( - QStringList(), DomEnvironment::Option::SingleThreaded, universePtr)); + envPtr = std::shared_ptr<DomEnvironment>( + new DomEnvironment(QStringList(), DomEnvironment::Option::SingleThreaded, + DomCreationOption::None, universePtr)); env = DomItem(envPtr); testOwnerPtr = std::shared_ptr<MockOwner>(new MockOwner( Path::Root(u"env").field(u"testOwner"), 0, @@ -363,7 +369,7 @@ private slots: auto tOwner3 = tOwner.path(u"$env.testOwner"); QCOMPARE(tOwner3.internalKind(), DomType::MockOwner); QList<qint64> values; - tOwner.visitTree(Path(), [&values](Path p, DomItem i, bool) { + tOwner.visitTree(Path(), [&values](const Path &p, DomItem i, bool) { if (i.pathFromOwner() != p) myErrors() .error(QStringLiteral(u"unexpected path %1 %2") @@ -403,18 +409,15 @@ private slots: qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, - univPtr)); + DomCreationOption::None, univPtr)); QQmlJS::Dom::DomItem env(envPtr); QVERIFY(env); QString testFile1 = baseDir + QLatin1String("/test1.qml"); DomItem tFile; - // env.loadBuiltins(); - env.loadFile( - testFile1, QString(), - [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, - LoadOption::DefaultLoad); - env.loadFile(baseDir, QString(), {}, LoadOption::DefaultLoad); - env.loadPendingDependencies(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir), {}); + envPtr->loadPendingDependencies(); QVERIFY(tFile); tFile = tFile.field(Fields::currentItem); @@ -424,7 +427,7 @@ private slots: DomItem obj1 = comp1.field(Fields::objects).index(0); QVERIFY(obj1); - tFile.visitTree(Path(), [&tFile](Path p, DomItem i, bool) { + tFile.visitTree(Path(), [&tFile](const Path &p, DomItem i, bool) { if (!(i == i.path(i.canonicalPath()))) { DomItem i2 = i.path(i.canonicalPath()); qDebug() << p << i.canonicalPath() << i.internalKindStr() << i2.internalKindStr() @@ -489,18 +492,16 @@ private slots: auto univPtr = std::shared_ptr<QQmlJS::Dom::DomUniverse>( new QQmlJS::Dom::DomUniverse(QLatin1String("univ1"))); auto envPtr = std::shared_ptr<QQmlJS::Dom::DomEnvironment>(new QQmlJS::Dom::DomEnvironment( - qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, univPtr)); + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, {}, univPtr)); QQmlJS::Dom::DomItem env(envPtr); QVERIFY(env); QString testFile1 = baseDir + QLatin1String("/test1.qml"); DomItem tFile; - env.loadBuiltins(); - env.loadFile( - testFile1, QString(), - [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, - LoadOption::DefaultLoad); - env.loadFile(baseDir, QString(), {}, LoadOption::DefaultLoad); - env.loadPendingDependencies(); + envPtr->loadBuiltins(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir), {}); + envPtr->loadPendingDependencies(); QVERIFY(tFile); tFile = tFile.field(Fields::currentItem); @@ -531,13 +532,13 @@ private slots: QList<DomItem> rects; obj1.resolve( Path::Current(PathCurrent::Lookup).field(Fields::type).key(u"Rectangle"_s), - [&rects](Path, DomItem &el) { + [&rects](Path, const DomItem &el) { rects.append(el); return true; }, {}); QVERIFY(rects.size() == 1); - for (DomItem &el : rects) { + for (const DomItem &el : rects) { QCOMPARE(rect.first(), el); } } @@ -548,6 +549,13 @@ private slots: DomItem f2 = env.path(p2); QVERIFY2(f2, "Directory dependencies did not load MySingleton.qml"); } + { + QString fPath = tFile.canonicalFilePath(); + QString fPath2 = fPath.mid(0, fPath.lastIndexOf(u'/')) % u"/ImportMeImplicitly.ui.qml"; + Path p2 = Paths::qmlFileObjectPath(fPath2); + DomItem f2 = env.path(p2); + QVERIFY2(f2, "Directory dependencies did not load .ui.qml file!"); + } } void testImports() @@ -558,17 +566,17 @@ private slots: using namespace Qt::StringLiterals; QString testFile1 = baseDir + QLatin1String("/TestImports.qml"); - DomItem env = DomEnvironment::create( + auto envPtr = DomEnvironment::create( QStringList(), QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); DomItem tFile; - env.loadFile( - testFile1, QString(), - [&tFile](Path, DomItem &, DomItem &newIt) { tFile = newIt.fileObject(); }, - LoadOption::DefaultLoad); - env.loadPendingDependencies(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1), + [&tFile](Path, const DomItem &, const DomItem &newIt) { + tFile = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); QVERIFY(tFile); QList<QmlUri> importedModules; @@ -601,17 +609,15 @@ private slots: { QString testFile = baseDir + QLatin1String("/test1.qml"); - DomItem env = DomEnvironment::create( + auto envPtr = DomEnvironment::create( qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); DomItem tFile; // place where to store the loaded file - env.loadFile( - testFile, QString(), - [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, - LoadOption::DefaultLoad); - env.loadPendingDependencies(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }); + envPtr->loadPendingDependencies(); DomItem f = tFile.fileObject(); QString dump1; f.dump([&dump1](QStringView v) { dump1.append(v); }); @@ -626,6 +632,7 @@ private slots: if (!diffs.isEmpty()) qDebug() << "testDeepCopy.diffs:" << diffs; QVERIFY(diffs.isEmpty()); + DomItem env(envPtr); DomItem univFile = env.universe().path(f.canonicalPath()); MutableDomItem univFileCopy = univFile.makeCopy(); QStringList univFileDiffs = @@ -645,11 +652,12 @@ private slots: void testInMemory() { DomItem res = DomItem::fromCode("MyItem{}"); + QCOMPARE(res.qmlObject(GoTo::Strict), DomItem()); DomItem obj = res.qmlObject(GoTo::MostLikely); QCOMPARE(obj.name(), u"MyItem"); } - static void checkAliases(DomItem &qmlObj) + static void checkAliases(const DomItem &qmlObj) { using namespace Qt::StringLiterals; @@ -714,7 +722,7 @@ private slots: } ++i; } - for (DomItem obj : qmlObj.children().values()) { + for (const DomItem &obj : qmlObj.children().values()) { if (obj.as<QmlObject>()) checkAliases(obj); } @@ -734,22 +742,3185 @@ private slots: QFETCH(QString, inFile); QString testFile1 = baseDir + u"/"_s + inFile; - DomItem env = DomEnvironment::create( + auto envPtr = DomEnvironment::create( QStringList(), QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); DomItem tFile; - env.loadFile( - testFile1, QString(), - [&tFile](Path, DomItem &, DomItem &newIt) { tFile = newIt.fileObject(); }, - LoadOption::DefaultLoad); - env.loadPendingDependencies(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1), + [&tFile](Path, const DomItem &, const DomItem &newIt) { + tFile = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); DomItem rootObj = tFile.qmlObject(GoTo::MostLikely); checkAliases(rootObj); } + void inlineComponents() + { + using namespace Qt::StringLiterals; + + QString testFile = baseDir + u"/inlineComponents.qml"_s; + + DomCreationOptions options{ DomCreationOption::WithScriptExpressions }; + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, options); + + DomItem tFile; + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile), + [&tFile](Path, const DomItem &, const DomItem &newIt) { + tFile = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + auto rootQmlObject = tFile.rootQmlObject(GoTo::MostLikely); + + // check if the lookup can find the inline components correctly, to see if the + // visitScopeChain also visit them. + auto ic3 = rootQmlObject.lookup("IC3", LookupType::Type, LookupOption::Normal, + [](const ErrorMessage &) {}); + + QCOMPARE(ic3.size(), 1); + QCOMPARE(ic3.front().name(), "inlineComponents.IC3"); + QCOMPARE(ic3.front().field(Fields::nameIdentifiers).internalKind(), DomType::ScriptType); + QCOMPARE(ic3.front() + .field(Fields::nameIdentifiers) + .field(Fields::typeName) + .value() + .toString(), + u"IC3"_s); + + auto ic1 = rootQmlObject.lookup("IC1", LookupType::Type, LookupOption::Normal, + [](const ErrorMessage &) {}); + + QCOMPARE(ic1.size(), 1); + QCOMPARE(ic1.front().name(), "inlineComponents.IC1"); + } + + void inlineObject() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/inlineObject.qml"_s; + + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + DomItem tFile; + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile), + [&tFile](Path, const DomItem &, const DomItem &newIt) { + tFile = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + auto rootQmlObject = tFile.rootQmlObject(GoTo::MostLikely); + + // check that the inline objects have their prototypes set. + + { + auto prototypes = rootQmlObject.propertyInfos() + .key(u"myItem"_s) + .field(Fields::bindings) + .index(0) + .field(Fields::value) + .field(Fields::prototypes); + QVERIFY(prototypes.internalKind() != DomType::Empty); + QCOMPARE(prototypes.indexes(), 1); + QCOMPARE(prototypes.index(0) + .field(Fields::referredObjectPath) + .as<ConstantData>() + ->value() + .toString(), + u"@lookup.type[\"Item\"]"_s); + } + + { + auto prototypes2 = rootQmlObject.propertyInfos() + .key(u"myItem2"_s) + .field(Fields::bindings) + .index(0) + .field(Fields::value) + .field(Fields::prototypes); + QVERIFY(prototypes2.internalKind() != DomType::Empty); + QCOMPARE(prototypes2.indexes(), 1); + QCOMPARE(prototypes2.index(0) + .field(Fields::referredObjectPath) + .as<ConstantData>() + ->value() + .toString(), + u"@lookup.type[\"IC\"]"_s); + } + } + + void scopesInDom() + { + QString fileName = baseDir + u"/checkScopes.qml"_s; + + const QStringList importPaths = { + QLibraryInfo::path(QLibraryInfo::QmlImportsPath), + }; + + DomItem tFile; + + auto envPtr = DomEnvironment::create( + importPaths, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, + WithSemanticAnalysis); + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, fileName), + [&tFile](Path, const DomItem &, const DomItem &newIt) { + tFile = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + auto root = tFile.rootQmlObject(GoTo::MostLikely); + + { + auto rootQmlObject = root.as<QmlObject>(); + QVERIFY(rootQmlObject); + auto rootScope = rootQmlObject->semanticScope(); + QVERIFY(rootScope); + QVERIFY(rootScope->hasOwnProperty("myInt")); + QVERIFY(rootScope->hasOwnProperty("myInt2")); + QVERIFY(rootScope->hasOwnPropertyBindings("myInt")); + QVERIFY(rootScope->hasOwnPropertyBindings("myInt2")); + } + } + + void propertyBindings() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/propertyBindings.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + // check the binding to a and b + DomItem a = rootQmlObject.path(".bindings[\"a\"][0].value.scriptElement.value"); + QCOMPARE(a.value().toDouble(), 42); + + DomItem b = rootQmlObject.path(".bindings[\"b\"][0].value.scriptElement.identifier"); + QCOMPARE(b.value().toString(), "a"); + + DomItem root = rootQmlObject.path(".idStr"); + QCOMPARE(root.value().toString(), "root"); + + DomItem componentIds = rootQmlObject.component().path(".ids"); + QCOMPARE(componentIds.keys(), QSet<QString>({ "root" })); + + DomItem idScriptExpression = + componentIds.key("root").index(0).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(idScriptExpression.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(idScriptExpression.field(Fields::identifier).value().toString(), "root"); + } + + void variableDeclarations() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/variableDeclarations.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + DomItem block = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement"); + + // check that variabledeclarationlists and statement lists are correctly collected + { + // let one = 1, two = 2, three = 3, four = 4, five = 5, six = 6 + std::vector<QString> data = { u"one"_s, u"two"_s, u"three"_s, + u"four"_s, u"five"_s, u"six"_s }; + DomItem block = + rootQmlObject.path(".methods[\"count\"][0].body.scriptElement.statements"); + DomItem variableDeclaration = block.index(0).field(Fields::declarations); + QCOMPARE((size_t)variableDeclaration.indexes(), data.size()); + for (size_t i = 0; i < data.size(); ++i) { + DomItem variableDeclarationEntry = variableDeclaration.index(i); + QCOMPARE(variableDeclarationEntry.field(Fields::identifier).value().toString(), + data[i]); + QCOMPARE((size_t)variableDeclarationEntry.field(Fields::initializer) + .value() + .toInteger(), + i + 1); + } + // let testMe = 123 + DomItem testDeclaration = block.index(1).field(Fields::declarations); + QCOMPARE(testDeclaration.indexes(), 1); + QCOMPARE(testDeclaration.index(0).field(Fields::identifier).value().toString(), + u"testMe"_s); + QCOMPARE(testDeclaration.index(0).field(Fields::initializer).value().toInteger(), + 123ll); + } + + // if there is a failure in the following line, then thats because either the + // variabledeclarationlist or the statement list was wrongly collected and the + // domcreator gave up on creating the Dom (unexpected DomType: DomType::ScriptExpression + // means that no Dom was constructed for f's body) + + // This block should have a semantic scope that defines sum and helloWorld + auto blockSemanticScope = block.semanticScope(); + QVERIFY(blockSemanticScope); + QVERIFY(blockSemanticScope); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"sum"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"helloWorld"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"a"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"b"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"aa"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"bb"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"bool1"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"bool2"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"nullVar"_s)); + DomItem statements = block.field(Fields::statements); + QCOMPARE(statements.indexes(), 8); + + // avoid that toInteger calls return 0 on error, which sometimes is the correct response. + const int invalidEnumValue = 9999; + + { + // let sum = 0, helloWorld = "hello" + DomItem variableDeclaration = statements.index(0).field(Fields::declarations); + QCOMPARE(variableDeclaration.indexes(), 2); + DomItem sumInitialization = variableDeclaration.index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(sumInitialization.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Let); + QCOMPARE(sumInitialization.field(Fields::identifier).value().toString(), "sum"); + QCOMPARE(sumInitialization.field(Fields::initializer) + .field(Fields::value) + .value() + .toDouble(), + 0); + + DomItem helloWorldInitialization = variableDeclaration.index(1); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(helloWorldInitialization.field(Fields::scopeType) + .value() + .toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Let); + QCOMPARE(helloWorldInitialization.field(Fields::identifier).value().toString(), + "helloWorld"); + QCOMPARE(helloWorldInitialization.field(Fields::initializer) + .field(Fields::value) + .value() + .toString(), + "hello"); + } + { + // const a = 3 + DomItem a = statements.index(1).field(Fields::declarations).index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(a.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Const); + QCOMPARE(a.field(Fields::identifier).value().toString(), "a"); + QCOMPARE(a.field(Fields::initializer).internalKind(), DomType::ScriptLiteral); + QCOMPARE(a.field(Fields::initializer).field(Fields::value).value().toInteger(), 3); + } + { + // const b = "patron" + DomItem b = statements.index(2).field(Fields::declarations).index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(b.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Const); + QCOMPARE(b.field(Fields::identifier).value().toString(), "b"); + QCOMPARE(b.field(Fields::initializer).internalKind(), DomType::ScriptLiteral); + QCOMPARE(b.field(Fields::initializer).field(Fields::value).value().toString(), + "patron"); + } + { + // var aa = helloWorld + DomItem aa = statements.index(3).field(Fields::declarations).index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(aa.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Var); + QCOMPARE(aa.field(Fields::identifier).value().toString(), "aa"); + QCOMPARE(aa.field(Fields::initializer).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(aa.field(Fields::initializer).field(Fields::identifier).value().toString(), + "helloWorld"); + // var bb = aa + DomItem bb = statements.index(3).field(Fields::declarations).index(1); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(bb.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Var); + QCOMPARE(bb.field(Fields::identifier).value().toString(), "bb"); + QCOMPARE(bb.field(Fields::initializer).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(bb.field(Fields::initializer).field(Fields::identifier).value().toString(), + "aa"); + } + { + // const bool1 = true + DomItem bool1 = statements.index(4).field(Fields::declarations).index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(bool1.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Const); + QCOMPARE(bool1.field(Fields::identifier).value().toString(), "bool1"); + QCOMPARE(bool1.field(Fields::initializer).internalKind(), DomType::ScriptLiteral); + QVERIFY(bool1.field(Fields::initializer).field(Fields::value).value().isTrue()); + } + { + // let bool2 = false + DomItem bool2 = statements.index(5).field(Fields::declarations).index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(bool2.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Let); + QCOMPARE(bool2.field(Fields::identifier).value().toString(), "bool2"); + QCOMPARE(bool2.field(Fields::initializer).internalKind(), DomType::ScriptLiteral); + QVERIFY(bool2.field(Fields::initializer).field(Fields::value).value().isFalse()); + } + { + // var nullVar = null + DomItem nullVar = statements.index(6).field(Fields::declarations).index(0); + QEXPECT_FAIL(0, "ScopeTypes not implmented yet, as not needed by qmlls for now!", + Continue); + QCOMPARE(nullVar.field(Fields::scopeType).value().toInteger(invalidEnumValue), + ScriptElements::VariableDeclarationEntry::ScopeType::Var); + QCOMPARE(nullVar.field(Fields::identifier).value().toString(), "nullVar"); + QCOMPARE(nullVar.field(Fields::initializer).internalKind(), DomType::ScriptLiteral); + QVERIFY(nullVar.field(Fields::initializer).field(Fields::value).value().isNull()); + } + } + + void ifStatements() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/ifStatements.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + DomItem block = rootQmlObject.path(".methods[\"conditional\"][0].body.scriptElement"); + auto blockSemanticScope = block.semanticScope(); + QVERIFY(blockSemanticScope); + + DomItem statements = block.field(Fields::statements); + QCOMPARE(statements.indexes(), 5); + + // let i = 5 + DomItem iDeclaration = statements.index(0); + QCOMPARE(iDeclaration.internalKind(), DomType::ScriptVariableDeclaration); + + { + // if (i) + // i = 42 + DomItem conditional = statements.index(1); + DomItem condition = conditional.field(Fields::condition); + QCOMPARE(condition.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(condition.field(Fields::identifier).value().toString(), u"i"_s); + + DomItem consequence = conditional.field(Fields::consequence); + auto nonBlockSemanticScope = consequence.semanticScope(); + QVERIFY(!nonBlockSemanticScope); // because there is no block + + QCOMPARE(consequence.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(consequence.field(Fields::left).field(Fields::identifier).value().toString(), + u"i"_s); + QCOMPARE(consequence.field(Fields::right).field(Fields::value).value().toDouble(), 42); + + QCOMPARE(conditional.field(Fields::alternative).internalKind(), DomType::Empty); + } + { + // if (i == 55) + // i = 32 + // else + // i = i - 1 + DomItem conditional = statements.index(2); + DomItem condition = conditional.field(Fields::condition); + QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 55); + + DomItem consequence = conditional.field(Fields::consequence); + auto nonBlockSemanticScope = consequence.semanticScope(); + QVERIFY(!nonBlockSemanticScope); + + QCOMPARE(consequence.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(consequence.field(Fields::left).field(Fields::identifier).value().toString(), + u"i"_s); + QCOMPARE(consequence.field(Fields::right).field(Fields::value).value().toDouble(), 32); + + DomItem alternative = conditional.field(Fields::alternative); + QCOMPARE(alternative.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(alternative.field(Fields::left).field(Fields::identifier).value().toString(), + u"i"_s); + QCOMPARE(alternative.field(Fields::right).internalKind(), + DomType::ScriptBinaryExpression); + } + { + // if (i == 42) { + // i = 111 + // } + DomItem conditional = statements.index(3); + DomItem condition = conditional.field(Fields::condition); + QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 42); + + DomItem consequence = conditional.field(Fields::consequence); + auto blockSemanticScope = consequence.semanticScope(); + QVERIFY(blockSemanticScope); + + QCOMPARE(consequence.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(consequence.field(Fields::statements).indexes(), 1); + DomItem consequence1 = consequence.field(Fields::statements).index(0); + QCOMPARE(consequence1.field(Fields::left).field(Fields::identifier).value().toString(), + u"i"_s); + QCOMPARE(consequence1.field(Fields::right).field(Fields::value).value().toDouble(), + 111); + + QCOMPARE(conditional.field(Fields::alternative).internalKind(), DomType::Empty); + } + { + // if (i == 746) { + // i = 123 + // } else { + // i = 456 + // } + + DomItem conditional = statements.index(4); + DomItem condition = conditional.field(Fields::condition); + QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 746); + + { + DomItem consequence = conditional.field(Fields::consequence); + QCOMPARE(consequence.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(consequence.field(Fields::statements).indexes(), 1); + DomItem consequence1 = consequence.field(Fields::statements).index(0); + QCOMPARE(consequence1.field(Fields::left) + .field(Fields::identifier) + .value() + .toString(), + u"i"_s); + QCOMPARE(consequence1.field(Fields::right).field(Fields::value).value().toDouble(), + 123); + } + + { + DomItem alternative = conditional.field(Fields::alternative); + auto blockSemanticScope = alternative.semanticScope(); + QVERIFY(blockSemanticScope); + + QCOMPARE(alternative.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(alternative.field(Fields::statements).indexes(), 1); + DomItem alternative1 = alternative.field(Fields::statements).index(0); + QCOMPARE(alternative1.field(Fields::left) + .field(Fields::identifier) + .value() + .toString(), + u"i"_s); + QCOMPARE(alternative1.field(Fields::right).field(Fields::value).value().toDouble(), + 456); + } + } + } + + void returnStatement() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/returnStatements.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + DomItem block = rootQmlObject.path(".methods[\"returningFunction\"][0].body.scriptElement"); + QCOMPARE(block.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(block.field(Fields::statements).indexes(), 1); + DomItem conditional = block.field(Fields::statements).index(0); + DomItem consequence = conditional.field(Fields::consequence); + QCOMPARE(consequence.internalKind(), DomType::ScriptReturnStatement); + { + DomItem returnValue = consequence.field(Fields::expression); + QCOMPARE(returnValue.internalKind(), DomType::ScriptLiteral); + QCOMPARE(returnValue.field(Fields::value).value().toDouble(), 123); + } + DomItem alternative = conditional.field(Fields::alternative); + QCOMPARE(alternative.internalKind(), DomType::ScriptReturnStatement); + { + DomItem returnValue = alternative.field(Fields::expression); + QCOMPARE(returnValue.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(returnValue.field(Fields::left).field(Fields::value).value().toDouble(), 1); + QCOMPARE(returnValue.field(Fields::right).field(Fields::value).value().toDouble(), 2); + } + } + + void forStatements() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/forStatements.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + DomItem block = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement"); + DomItem statements = block.field(Fields::statements); + DomItem forLoop = statements.index(1); + { + // for ( >> let i = 0 << ; i < 100; i = i + 1) { + DomItem declarationList = + forLoop.field(Fields::declarations).field(Fields::declarations); + QCOMPARE(declarationList.internalKind(), DomType::List); + + QCOMPARE(declarationList.indexes(), 1); + DomItem declaration = declarationList.index(0); + + QCOMPARE(declaration.internalKind(), DomType::ScriptVariableDeclarationEntry); + QCOMPARE(declaration.field(Fields::initializer).internalKind(), DomType::ScriptLiteral); + + QCOMPARE(declaration.field(Fields::identifier).value().toString(), "i"); + QCOMPARE(declaration.field(Fields::initializer).field(Fields::value).value().toDouble(), + 0); + } + { + // for ( let i = 0; >> i < 100 <<; i = i + 1) { + DomItem condition = forLoop.field(Fields::condition); + QCOMPARE(condition.internalKind(), DomType::ScriptBinaryExpression); + + QCOMPARE(condition.field(Fields::left).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(condition.field(Fields::left).field(Fields::identifier).value().toString(), + "i"); + + QCOMPARE(condition.field(Fields::right).internalKind(), DomType::ScriptLiteral); + QCOMPARE(condition.field(Fields::right).field(Fields::value).value().toDouble(), 100); + } + { + // for ( let i = 0; i < 100; >> i = i + 1 << ) { + DomItem expression = forLoop.field(Fields::expression); + QCOMPARE(expression.internalKind(), DomType::ScriptBinaryExpression); + DomItem left = expression.field(Fields::left); + QCOMPARE(left.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(left.field(Fields::identifier).value().toString(), "i"); + + // for ( let i = 0; i < 100; i = >> i + 1 << ) { + DomItem right = expression.field(Fields::right); + QCOMPARE(right.internalKind(), DomType::ScriptBinaryExpression); + DomItem left2 = right.field(Fields::left); + QCOMPARE(left2.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(left2.field(Fields::identifier).value().toString(), "i"); + + DomItem right2 = right.field(Fields::right); + QCOMPARE(right2.internalKind(), DomType::ScriptLiteral); + QCOMPARE(right2.field(Fields::value).value().toDouble(), 1); + } + { + // test the body of the for-loop + DomItem body = forLoop.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto blockSemanticScope = body.semanticScope(); + QVERIFY(blockSemanticScope); + + DomItem statementList = body.field(Fields::statements); + QCOMPARE(statementList.indexes(), 2); + { + // >> sum = sum + 1 << + DomItem binaryExpression = statementList.index(0); + QCOMPARE(binaryExpression.internalKind(), DomType::ScriptBinaryExpression); + + DomItem left = binaryExpression.field(Fields::left); + QCOMPARE(left.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(left.field(Fields::identifier).value().toString(), "sum"); + + // sum = >> sum + 1 << + DomItem right = binaryExpression.field(Fields::right); + QCOMPARE(right.internalKind(), DomType::ScriptBinaryExpression); + + DomItem left2 = right.field(Fields::left); + QCOMPARE(left2.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(left2.field(Fields::identifier).value().toString(), "sum"); + + DomItem right2 = right.field(Fields::right); + QCOMPARE(right2.internalKind(), DomType::ScriptLiteral); + QCOMPARE(right2.field(Fields::value).value().toDouble(), 1); + } + + { + // >> for (;;) << + // i = 42 + DomItem innerForLoop = statementList.index(1); + QCOMPARE(innerForLoop.internalKind(), DomType::ScriptForStatement); + QCOMPARE(innerForLoop.field(Fields::declarations).indexes(), 0); + QVERIFY(!innerForLoop.field(Fields::initializer)); + QVERIFY(!innerForLoop.field(Fields::condition)); + QVERIFY(!innerForLoop.field(Fields::expression)); + QVERIFY(innerForLoop.field(Fields::body)); + + // for (;;) + // >> i = 42 << + DomItem expression = innerForLoop.field(Fields::body); + QCOMPARE(expression.internalKind(), DomType::ScriptBinaryExpression); + + DomItem left = expression.field(Fields::left); + QCOMPARE(left.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(left.field(Fields::identifier).value().toString(), "i"); + + DomItem right = expression.field(Fields::right); + QCOMPARE(right.internalKind(), DomType::ScriptLiteral); + QCOMPARE(right.field(Fields::value).value().toDouble(), 42); + } + } + } + + void nullStatements() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/nullStatements.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + DomItem block = rootQmlObject.methods() + .key(u"testForNull"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement); + + QVERIFY(block); + // First for + DomItem statements = block.field(Fields::statements); + QCOMPARE(statements.internalKind(), DomType::List); + QVERIFY(statements.index(0).field(Fields::body).field(Fields::statements).internalKind() + != DomType::Empty); + QCOMPARE(statements.index(0).field(Fields::body).field(Fields::statements).length(), 0); + + // Second for + DomItem secondFor = statements.index(1).field(Fields::body); + QVERIFY(secondFor.internalKind() == DomType::ScriptIdentifierExpression); + QCOMPARE(secondFor.field(Fields::identifier).value().toString(), u"x"_s); + + // Empty block + QVERIFY(statements.index(3).field(Fields::statements).internalKind() != DomType::Empty); + QCOMPARE(statements.index(3).field(Fields::statements).length(), 0); + } + + void deconstruction() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/callExpressions.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + + DomItem method = rootQmlObject.field(Fields::methods).key(u"deconstruct"_s).index(0); + DomItem statement = method.field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0); + QCOMPARE(statement.internalKind(), DomType::ScriptVariableDeclaration); + QCOMPARE(statement.field(Fields::declarations).indexes(), 3); + + { + DomItem entry = statement.field(Fields::declarations).index(0); + QVERIFY(!entry.field(Fields::identifier)); + DomItem deconstructedProperty = + entry.field(Fields::bindingElement).field(Fields::properties); + + QCOMPARE(deconstructedProperty.indexes(), 1); + QCOMPARE(deconstructedProperty.index(0).field(Fields::name).value().toString(), u"a"_s); + + DomItem initializer = entry.field(Fields::initializer).field(Fields::properties); + QCOMPARE(initializer.indexes(), 2); + + DomItem a32 = initializer.index(0); + QCOMPARE(a32.field(Fields::initializer).value().toInteger(), 32); + QCOMPARE(a32.field(Fields::name).value().toString(), "a"); + + DomItem b42 = initializer.index(1); + QCOMPARE(b42.field(Fields::initializer).value().toInteger(), 42); + QCOMPARE(b42.field(Fields::name).value().toString(), "b"); + } + { + DomItem entry = statement.field(Fields::declarations).index(1); + QVERIFY(!entry.field(Fields::identifier)); + DomItem deconstructedProperty = + entry.field(Fields::bindingElement).field(Fields::properties); + + QCOMPARE(deconstructedProperty.indexes(), 2); + QCOMPARE(deconstructedProperty.index(0).field(Fields::name).value().toString(), u"b"_s); + QCOMPARE(deconstructedProperty.index(1).field(Fields::name).value().toString(), u"c"_s); + + DomItem initializer = entry.field(Fields::initializer).field(Fields::properties); + QCOMPARE(initializer.indexes(), 2); + + DomItem a32 = initializer.index(0); + QCOMPARE(a32.field(Fields::initializer).value().toInteger(), 32); + QCOMPARE(a32.field(Fields::name).value().toString(), "b"); + + DomItem b42 = initializer.index(1); + QCOMPARE(b42.field(Fields::initializer).value().toInteger(), 42); + QCOMPARE(b42.field(Fields::name).value().toString(), "c"); + } + { + DomItem entry = statement.field(Fields::declarations).index(2); + QVERIFY(!entry.field(Fields::identifier)); + DomItem deconstructedProperty = + entry.field(Fields::bindingElement).field(Fields::elements); + + QCOMPARE(deconstructedProperty.indexes(), 3); + QCOMPARE(deconstructedProperty.index(0).field(Fields::identifier).value().toString(), + u"d"_s); + QCOMPARE(deconstructedProperty.index(1).field(Fields::identifier).value().toString(), + u"e"_s); + QCOMPARE(deconstructedProperty.index(2).field(Fields::identifier).value().toString(), + u"f"_s); + + DomItem initializer = entry.field(Fields::initializer).field(Fields::elements); + QCOMPARE(initializer.indexes(), 3); + + for (int i = 0; i < initializer.indexes(); ++i) { + QCOMPARE(initializer.index(i).field(Fields::initializer).value().toInteger(), + (i + 1) * 111); + } + } + { + DomItem statement = method.field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1); + DomItem listWithElision = statement.field(Fields::declarations) + .index(0) + .field(Fields::initializer) + .field(Fields::elements); + const int listElements = 6; + const int elisionCount = 4; + QCOMPARE(listWithElision.indexes(), listElements); + for (int i = 0; i < elisionCount; ++i) { + QCOMPARE(listWithElision.index(i).internalKind(), DomType::ScriptElision); + } + QCOMPARE(listWithElision.index(elisionCount).internalKind(), DomType::ScriptArrayEntry); + QCOMPARE(listWithElision.index(elisionCount + 1).internalKind(), + DomType::ScriptElision); + } + } + + void callExpressions() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/callExpressions.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + + { + DomItem p1 = rootQmlObject.path(".bindings[\"p\"][0].value.scriptElement"); + QCOMPARE(p1.internalKind(), DomType::ScriptCallExpression); + QCOMPARE(p1.field(Fields::callee).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p1.field(Fields::callee).field(Fields::identifier).value().toString(), "f"); + QCOMPARE(p1.field(Fields::arguments).internalKind(), DomType::List); + QCOMPARE(p1.field(Fields::arguments).indexes(), 0); + } + + { + DomItem p2 = rootQmlObject.path(".bindings[\"p2\"][0].value.scriptElement"); + QCOMPARE(p2.internalKind(), DomType::ScriptCallExpression); + QCOMPARE(p2.field(Fields::callee).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p2.field(Fields::callee).field(Fields::identifier).value().toString(), "f"); + + DomItem p2List = p2.field(Fields::arguments); + QCOMPARE(p2List.indexes(), 20); + for (int i = 0; i < p2List.indexes(); ++i) { + QCOMPARE(p2List.index(i).internalKind(), DomType::ScriptLiteral); + QCOMPARE(p2List.index(i).field(Fields::value).value().toInteger(), i + 1); + } + } + + { + DomItem p3 = rootQmlObject.path(".bindings[\"p3\"][0].value.scriptElement"); + QCOMPARE(p3.internalKind(), DomType::ScriptCallExpression); + QCOMPARE(p3.field(Fields::callee).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p3.field(Fields::callee).field(Fields::identifier).value().toString(), "evil"); + + DomItem p3List = p3.field(Fields::arguments); + QCOMPARE(p3List.indexes(), 3); + + DomItem firstArg = p3List.index(0); + QCOMPARE(firstArg.internalKind(), DomType::ScriptObject); + + { + DomItem helloWorld = firstArg.field(Fields::properties).index(0); + QCOMPARE(helloWorld.internalKind(), DomType::ScriptProperty); + QCOMPARE(helloWorld.field(Fields::initializer).value().toString(), "World"); + QCOMPARE(helloWorld.field(Fields::name).value().toString(), "hello"); + } + + { + DomItem yyyy = firstArg.field(Fields::properties).index(1); + QCOMPARE(yyyy.internalKind(), DomType::ScriptProperty); + QCOMPARE(yyyy.field(Fields::initializer).value().toString(), "yyy"); + QCOMPARE(yyyy.field(Fields::name).value().toString(), "y"); + } + + DomItem secondArg = p3List.index(1); + QCOMPARE(secondArg.internalKind(), DomType::ScriptArray); + { + for (int i = 0; i < 3; ++i) { + DomItem current = secondArg.field(Fields::elements).index(i); + QCOMPARE(current.internalKind(), DomType::ScriptArrayEntry); + QCOMPARE(current.field(Fields::initializer).value().toInteger(), i + 1); + } + } + + DomItem thirdArg = p3List.index(2); + QCOMPARE(thirdArg.internalKind(), DomType::ScriptObject); + { + DomItem is = thirdArg.field(Fields::properties).index(0); + QCOMPARE(is.internalKind(), DomType::ScriptProperty); + QCOMPARE(is.field(Fields::name).value().toString(), "is"); + DomItem initializer = is.field(Fields::initializer).field(Fields::properties); + + QCOMPARE(initializer.index(0).field(Fields::name).value().toString(), "a"); + QCOMPARE(initializer.index(0).field(Fields::initializer).value().toInteger(), 111); + + QCOMPARE(initializer.index(1).field(Fields::name).value().toString(), "lot"); + QCOMPARE(initializer.index(1).field(Fields::initializer).value().toInteger(), 222); + + QCOMPARE(initializer.index(2).field(Fields::name).value().toString(), "of"); + QCOMPARE(initializer.index(2).field(Fields::initializer).value().toInteger(), 333); + + QCOMPARE(initializer.index(3).field(Fields::name).value().toString(), "fun"); + QCOMPARE(initializer.index(3).field(Fields::initializer).value().toInteger(), 444); + + QCOMPARE(initializer.index(4).field(Fields::name).value().toString(), "really"); + QCOMPARE(initializer.index(4) + .field(Fields::initializer) + .field(Fields::elements) + .index(0) + .field(Fields::initializer) + .value() + .toString(), + "!"); + } + } + + { + DomItem functionFs = rootQmlObject.field(Fields::methods).key(u"f"); + QCOMPARE(functionFs.indexes(), 1); + DomItem functionF = functionFs.index(0); + + QVERIFY(!functionF.semanticScope().isNull()); + QVERIFY(functionF.semanticScope()->ownJSIdentifier("helloF").has_value()); + DomItem parameters = functionF.field(Fields::parameters); + std::vector<QString> parameterNames = { + u"q"_s, u"w"_s, u"e"_s, u"r"_s, u"t"_s, u"y"_s + }; + QCOMPARE(parameterNames.size(), (size_t)parameters.indexes()); + + for (size_t i = 0; i < parameterNames.size(); ++i) { + DomItem currentParameter = parameters.index(i); + QCOMPARE(currentParameter.field(Fields::name).value().toString(), + parameterNames[i]); + } + } + + { + DomItem functionFWithDefaults = + rootQmlObject.field(Fields::methods).key(u"fWithDefault"); + QCOMPARE(functionFWithDefaults.indexes(), 1); + DomItem functionFWithDefault = functionFWithDefaults.index(0); + QVERIFY(!functionFWithDefault.semanticScope().isNull()); + QVERIFY(functionFWithDefault.semanticScope()->ownJSIdentifier(u"helloFWithDefault"_s)); + DomItem parameters = functionFWithDefault.field(Fields::parameters); + + const std::vector<QString> parameterNames = { u"q"_s, u"w"_s, u"e"_s, + u"r"_s, u"t"_s, u"y"_s }; + const QSet<QString> parameterWithoutDefault = { u"e"_s, u"t"_s }; + + QCOMPARE(parameterNames.size(), (size_t)parameters.indexes()); + + for (size_t i = 0; i < parameterNames.size(); ++i) { + DomItem currentParameter = parameters.index(i); + QCOMPARE(currentParameter.field(Fields::name).value().toString(), + parameterNames[i]); + + DomItem scriptElement = + currentParameter.field(Fields::value).field(Fields::scriptElement); + QCOMPARE(scriptElement.internalKind(), DomType::ScriptFormalParameter); + QCOMPARE(scriptElement.field(Fields::identifier).value().toString(), + parameterNames[i]); + if (!parameterWithoutDefault.contains(parameterNames[i])) { + QCOMPARE((size_t)scriptElement.field(Fields::initializer).value().toInteger(), + i + 1); + } + } + } + + { + // note: no need to check the inside of ScriptObject and ScriptArray as those are + // already tested in deconstruction(). + DomItem method = rootQmlObject.field(Fields::methods).key(u"evil"_s).index(0); + QVERIFY(!method.semanticScope().isNull()); + QVERIFY(method.semanticScope()->ownJSIdentifier("helloEvil").has_value()); + DomItem parameters = method.field(Fields::parameters); + QCOMPARE(parameters.indexes(), 3); + + { + DomItem parameter1 = + parameters.index(0).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(parameter1.internalKind(), DomType::ScriptFormalParameter); + + DomItem objectPattern = parameter1.field(Fields::bindingElement); + QCOMPARE(objectPattern.internalKind(), DomType::ScriptObject); + QCOMPARE(objectPattern.field(Fields::properties).indexes(), 2); + } + { + DomItem parameter2 = + parameters.index(1).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(parameter2.internalKind(), DomType::ScriptFormalParameter); + + DomItem objectPattern = parameter2.field(Fields::bindingElement); + QCOMPARE(objectPattern.internalKind(), DomType::ScriptArray); + QCOMPARE(objectPattern.field(Fields::elements).indexes(), 3); + } + { + DomItem parameter3 = + parameters.index(2).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(parameter3.internalKind(), DomType::ScriptFormalParameter); + + DomItem objectPattern = parameter3.field(Fields::bindingElement); + QCOMPARE(objectPattern.internalKind(), DomType::ScriptObject); + QCOMPARE(objectPattern.field(Fields::properties).indexes(), 3); + + DomItem parameter3DefaultValue = parameter3.field(Fields::initializer); + QCOMPARE(parameter3DefaultValue.internalKind(), DomType::ScriptObject); + + DomItem objectPatternDefaultValue = + parameter3DefaultValue.field(Fields::properties); + QCOMPARE(objectPatternDefaultValue.indexes(), 3); + } + } + { + DomItem method = rootQmlObject.field(Fields::methods).key(u"marmelade"_s).index(0); + QVERIFY(!method.semanticScope().isNull()); + QVERIFY(method.semanticScope()->ownJSIdentifier(u"helloMarmelade"_s)); + DomItem parameters = method.field(Fields::parameters); + QCOMPARE(parameters.indexes(), 1); + { + DomItem parameter1 = + parameters.index(0).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(parameter1.internalKind(), DomType::ScriptFormalParameter); + QCOMPARE(parameter1.field(Fields::identifier).value().toString(), "onTheBread"); + } + } + { + DomItem method = rootQmlObject.field(Fields::methods).key(u"marmelade2"_s).index(0); + QVERIFY(!method.semanticScope().isNull()); + QVERIFY(method.semanticScope()->ownJSIdentifier(u"helloMarmelade2"_s)); + DomItem parameters = method.field(Fields::parameters); + QCOMPARE(parameters.indexes(), 3); + { + DomItem parameter3 = + parameters.index(2).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(parameter3.internalKind(), DomType::ScriptFormalParameter); + QCOMPARE(parameter3.field(Fields::identifier).value().toString(), "onTheBread"); + } + } + { + DomItem method = rootQmlObject.field(Fields::methods).key(u"withTypes"_s).index(0); + QVERIFY(!method.semanticScope().isNull()); + QCOMPARE(method.semanticScope()->scopeType(), + QQmlJSScope::ScopeType::JSFunctionScope); + DomItem parameters = method.field(Fields::parameters); + QCOMPARE(parameters.indexes(), 2); + QCOMPARE(parameters.index(0) + .field(Fields::value) + .field(Fields::scriptElement) + .field(Fields::type) + .field(Fields::typeName) + .value() + .toString(), + "int"); + QCOMPARE(parameters.index(1) + .field(Fields::value) + .field(Fields::scriptElement) + .field(Fields::type) + .field(Fields::typeName) + .value() + .toString(), + "MyType"); + } + { + DomItem method = rootQmlObject.field(Fields::methods).key(u"empty"_s).index(0); + QVERIFY(!method.semanticScope().isNull()); + QCOMPARE(method.semanticScope()->scopeType(), + QQmlJSScope::ScopeType::JSFunctionScope); + } + { + DomItem method = rootQmlObject.field(Fields::methods).key(u"mySignal"_s).index(0); + // signals contain the scope of the QML object owning them + QVERIFY(!method.semanticScope().isNull()); + QCOMPARE(method.semanticScope()->scopeType(), QQmlJSScope::ScopeType::QMLScope); + } + } + + void fieldMemberExpression() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/fieldMemberExpression.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + + { + DomItem p1 = rootQmlObject.path(".bindings[\"p1\"][0].value.scriptElement"); + QCOMPARE(p1.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p1.field(Fields::identifier).value().toString(), "p"); + } + + { + DomItem p1Qualified = + rootQmlObject.path(".bindings[\"p1Qualified\"][0].value.scriptElement"); + fieldMemberExpressionHelper(p1Qualified, QStringList{ u"p"_s }); + QCOMPARE(p1Qualified.field(Fields::left).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(p1Qualified.field(Fields::left).field(Fields::identifier).value().toString(), + "root"); + } + { + // property var p1Bracket: root["p"] + DomItem p1 = rootQmlObject.path(".bindings[\"p1Bracket\"][0].value.scriptElement"); + QCOMPARE(p1.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(p1.field(Fields::operation).value().toInteger(), + ScriptElements::BinaryExpression::ArrayMemberAccess); + + QCOMPARE(p1.field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p1.field(Fields::left).field(Fields::identifier).value().toString(), "root"); + + QCOMPARE(p1.field(Fields::right).internalKind(), DomType::ScriptLiteral); + QCOMPARE(p1.field(Fields::right).field(Fields::value).value().toString(), "p"); + } + { + // property var p1Index: root[42] + DomItem p1 = rootQmlObject.path(".bindings[\"p1Index\"][0].value.scriptElement"); + QCOMPARE(p1.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(p1.field(Fields::operation).value().toInteger(), + ScriptElements::BinaryExpression::ArrayMemberAccess); + + QCOMPARE(p1.field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p1.field(Fields::left).field(Fields::identifier).value().toString(), "root"); + + QCOMPARE(p1.field(Fields::right).internalKind(), DomType::ScriptLiteral); + QCOMPARE(p1.field(Fields::right).field(Fields::value).value().toInteger(), 42); + } + { + // property var p1Key: root[p] + DomItem p1 = rootQmlObject.path(".bindings[\"p1Key\"][0].value.scriptElement"); + QCOMPARE(p1.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(p1.field(Fields::operation).value().toInteger(), + ScriptElements::BinaryExpression::ArrayMemberAccess); + + QCOMPARE(p1.field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p1.field(Fields::left).field(Fields::identifier).value().toString(), "root"); + + QCOMPARE(p1.field(Fields::right).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p1.field(Fields::right).field(Fields::identifier).value().toString(), "p"); + } + { + DomItem p1Qualified = + rootQmlObject.path(".bindings[\"p1Qualified\"][0].value.scriptElement"); + fieldMemberExpressionHelper(p1Qualified, QStringList{ u"p"_s }); + QCOMPARE(p1Qualified.field(Fields::left).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(p1Qualified.field(Fields::left).field(Fields::identifier).value().toString(), + "root"); + } + + { + DomItem p3 = rootQmlObject.path(".bindings[\"p3\"][0].value.scriptElement"); + fieldMemberExpressionHelper(p3, QStringList{ u"property2"_s, u"p"_s }); + DomItem p3Left = p3.field(Fields::left).field(Fields::left); + QCOMPARE(p3Left.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p3Left.field(Fields::identifier).value().toString(), "property1"); + } + + { + DomItem p3Qualified = + rootQmlObject.path(".bindings[\"p3Qualified\"][0].value.scriptElement"); + fieldMemberExpressionHelper(p3Qualified, + QStringList{ u"property1"_s, u"property2"_s, u"p"_s }); + DomItem p3Left = + p3Qualified.field(Fields::left).field(Fields::left).field(Fields::left); + QCOMPARE(p3Left.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(p3Left.field(Fields::identifier).value().toString(), "root"); + } + } + + void switchStatement() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/switchStatement.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + QVERIFY(rootQmlObject); + + { + DomItem statements = rootQmlObject.methods() + .key(u"switchStatement") + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements); + QVERIFY(statements); + QCOMPARE(statements.index(0).internalKind(), DomType::ScriptVariableDeclaration); + QCOMPARE(statements.index(1).internalKind(), DomType::ScriptVariableDeclaration); + + DomItem firstSwitch = statements.index(2); + QVERIFY(firstSwitch); + QCOMPARE(firstSwitch.internalKind(), DomType::ScriptSwitchStatement); + QCOMPARE(firstSwitch.field(Fields::caseBlock).internalKind(), DomType::ScriptCaseBlock); + QCOMPARE(firstSwitch.field(Fields::expression).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(firstSwitch.field(Fields::expression) + .field(Fields::identifier) + .value() + .toString(), + u"animals"); + + DomItem caseClauses = firstSwitch.field(Fields::caseBlock).field(Fields::caseClauses); + QVERIFY(caseClauses); + QCOMPARE(caseClauses.index(0).internalKind(), DomType::ScriptCaseClause); + QCOMPARE(caseClauses.index(0).field(Fields::expression).internalKind(), + DomType::ScriptLiteral); + QCOMPARE(caseClauses.index(0).field(Fields::expression).value().toString(), u"cat"); + + DomItem innerSwitch = caseClauses.index(0).field(Fields::statements).index(0); + QVERIFY(innerSwitch); + QCOMPARE(innerSwitch.internalKind(), DomType::ScriptSwitchStatement); + QCOMPARE(innerSwitch.field(Fields::expression).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(innerSwitch.field(Fields::expression).value().toString(), u"no"); + + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(0) + .internalKind(), + DomType::ScriptCaseClause); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(0) + .field(Fields::expression) + .internalKind(), + DomType::ScriptLiteral); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(0) + .field(Fields::expression) + .value() + .toInteger(), + 0); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(0) + .field(Fields::statements) + .index(0) + .field(Fields::expression) + .value() + .toString(), + u"patron"); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(1) + .internalKind(), + DomType::ScriptCaseClause); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(1) + .field(Fields::statements) + .index(0) + .field(Fields::expression) + .value() + .toString(), + u"mafik"); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::defaultClause) + .internalKind(), + DomType::ScriptDefaultClause); + QCOMPARE(innerSwitch.field(Fields::caseBlock) + .field(Fields::defaultClause) + .field(Fields::statements) + .index(0) + .field(Fields::expression) + .value() + .toString(), + u"none"); + + // case "dog" + DomItem caseDogBlock = firstSwitch.field(Fields::caseBlock) + .field(Fields::caseClauses) + .index(1) + .field(Fields::statements) + .index(0); + QVERIFY(caseDogBlock); + // Check if semantic scope is correctly created for the CaseClause + auto blockSemanticScope = caseDogBlock.semanticScope(); + QVERIFY(blockSemanticScope); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"name"_s)); + QVERIFY(blockSemanticScope->ownJSIdentifier(u"another"_s)); + + // Default clause + DomItem defaultClause = + firstSwitch.field(Fields::caseBlock).field(Fields::defaultClause); + QVERIFY(defaultClause); + QCOMPARE(defaultClause.internalKind(), DomType::ScriptDefaultClause); + QCOMPARE(defaultClause.field(Fields::statements).index(0).internalKind(), + DomType::ScriptReturnStatement); + QCOMPARE(defaultClause.field(Fields::statements) + .index(0) + .field(Fields::expression) + .internalKind(), + DomType::ScriptLiteral); + QCOMPARE(defaultClause.field(Fields::statements) + .index(0) + .field(Fields::expression) + .value() + .toString(), + u"monster"); + + // case "moreCases!" + const DomItem moreCases = firstSwitch.field(Fields::caseBlock) + .field(Fields::moreCaseClauses) + .index(0) + .field(Fields::statements) + .index(0); + + QCOMPARE(moreCases.internalKind(), + DomType::ScriptReturnStatement); + QCOMPARE(moreCases.field(Fields::expression).value().toString(), u"moreCaseClauses?"); + } + } + + 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); + DomItem whileBody = whileTest.field(Fields::body); + QVERIFY(whileBody); + QCOMPARE(whileBody.internalKind(), DomType::ScriptBlockStatement); + auto scope = whileBody.semanticScope(); + 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->jsIdentifier("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); + DomItem doWhileBody = doWhile.field(Fields::body); + QVERIFY(doWhileBody); + auto doWhileBodyScope = doWhileBody.semanticScope(); + QVERIFY(doWhileBodyScope); + QVERIFY(doWhileBodyScope->ownJSIdentifier("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->jsIdentifier("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->jsIdentifier("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->jsIdentifier("a")); // var [a,b,c,d] + QVERIFY(outerForEachScope->jsIdentifier("b")); + QVERIFY(outerForEachScope->jsIdentifier("c")); + QVERIFY(outerForEachScope->jsIdentifier("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->ownJSIdentifier("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->jsIdentifier("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->ownJSIdentifier("a1")); // const [a1,,a2,...rest] + QVERIFY(secondForEachScope->ownJSIdentifier("a2")); + QVERIFY(secondForEachScope->ownJSIdentifier("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->ownJSIdentifier("t")); + } + } + + void doNotCrashEcmaScriptClass() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/ecmaScriptClass.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + QVERIFY(rootQmlObject); + } + + void bindingAttachedOrGroupedProperties() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/attachedOrGroupedProperties.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + QVERIFY(rootQmlObject); + + DomItem dotNotation = rootQmlObject.path(u".children[0].bindings[\"grouped.font.family\"][0].bindingIdentifiers"); + QVERIFY(dotNotation); + QCOMPARE(dotNotation.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(dotNotation.field(Fields::left).internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(dotNotation.field(Fields::right).field(Fields::identifier).value().toString(), u"family"); + QCOMPARE(dotNotation.field(Fields::right).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(dotNotation.field(Fields::left).field(Fields::left).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(dotNotation.field(Fields::left).field(Fields::left).field(Fields::identifier).value().toString(), u"grouped"); + QCOMPARE(dotNotation.field(Fields::left).field(Fields::right).internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(dotNotation.field(Fields::left).field(Fields::right).field(Fields::identifier).value().toString(), u"font"); + auto dotNotationScope = dotNotation.semanticScope(); + QVERIFY(!dotNotationScope); + + DomItem groupNotationChild1 = rootQmlObject.path(u".children[1].children[0]"); + QVERIFY(groupNotationChild1); + QCOMPARE(groupNotationChild1.internalKind(), DomType::QmlObject); + QCOMPARE(groupNotationChild1.field(Fields::name).value().toString(), u"myText"); + auto myTextScope = groupNotationChild1.semanticScope(); + QVERIFY(myTextScope); + QCOMPARE(myTextScope->scopeType(), QQmlJSScope::ScopeType::GroupedPropertyScope); + QVERIFY(myTextScope->hasProperty("font")); + + DomItem groupNotationChild2 = groupNotationChild1.path(u".children[0]"); + QCOMPARE(groupNotationChild2.internalKind(), DomType::QmlObject); + QCOMPARE(groupNotationChild2.field(Fields::name).value().toString(), u"font"); + + auto fontScope = groupNotationChild2.semanticScope(); + QVERIFY(fontScope); + QCOMPARE(fontScope->scopeType(), QQmlJSScope::ScopeType::GroupedPropertyScope); + QVERIFY(fontScope->hasProperty("pixelSize")); + + DomItem pixelSize = groupNotationChild2.path(u".bindings[\"pixelSize\"][0].bindingIdentifiers"); + QCOMPARE(pixelSize.internalKind(), DomType::ScriptIdentifierExpression); + QCOMPARE(pixelSize.field(Fields::identifier).value().toString(), u"pixelSize"); + + DomItem attached = rootQmlObject.path(u".bindings[\"Keys.onPressed\"][0].bindingIdentifiers"); + QVERIFY(attached); + QCOMPARE(attached.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(attached.field(Fields::left).field(Fields::identifier).value().toString(), u"Keys"); + QCOMPARE(attached.field(Fields::right).field(Fields::identifier).value().toString(), u"onPressed"); + } + + void enumDeclarations() + { + using namespace Qt::StringLiterals; + QString testFile = baseDir + u"/enumDeclarations.qml"_s; + DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + QVERIFY(fileObject); + DomItem enums = fileObject.path(u".components[\"\"][0].enumerations"); + QVERIFY(enums); + + DomItem catsEnum = enums.key("Cats").index(0); + QVERIFY(catsEnum); + QCOMPARE(catsEnum.internalKind(), DomType::EnumDecl); + QCOMPARE(catsEnum.name(), u"Cats"); + + auto values = catsEnum.field(Fields::values); + QCOMPARE(values.length(), 3); + QCOMPARE(values.index(0).internalKind(), DomType::EnumItem); + QCOMPARE(values.index(0).name(), u"Patron"); + QCOMPARE(values.index(0).field(Fields::value).value().toInteger(), 0); + QCOMPARE(values.index(1).internalKind(), DomType::EnumItem); + QCOMPARE(values.index(1).name(), u"Mafya"); + QCOMPARE(values.index(1).field(Fields::value).value().toInteger(), 1); + QCOMPARE(values.index(2).internalKind(), DomType::EnumItem); + QCOMPARE(values.index(2).name(), u"Kivrik"); + QCOMPARE(values.index(2).field(Fields::value).value().toInteger(), -1); + } + + void tryStatements() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/tryStatements.qml"_s; + const DomItem root = rootQmlObjectFromFile(testFile, qmltypeDirs); + QVERIFY(root); + const DomItem statements = root.path(u".methods[\"f\"][0].body.scriptElement.statements"); + QCOMPARE(statements.indexes(), 3); + + // test the try blocks + for (int i = 0; i < 3; ++i) { + const DomItem statement = statements.index(i).field(Fields::block); + QVERIFY(statement); + QCOMPARE(statement.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(statement.field(Fields::statements) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"insideTry"_s); + } + + // test the catch blocks + for (int i = 0; i < 3; ++i) { + const DomItem statement = statements.index(i).field(Fields::catchBlock); + if (i == 2) { + QVERIFY(!statement); // no catch in last statement + continue; + } + + QVERIFY(statement); + QCOMPARE(statement.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(statement.field(Fields::statements) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"insideCatch"_s); + + const DomItem expression = statements.index(i).field(Fields::catchParameter); + QVERIFY(expression); + QCOMPARE(expression.field(Fields::identifier) + .value() + .toString(), + u"catchExpression"_s); + } + + // test the finally blocks + for (int i = 0; i < 3; ++i) { + const DomItem statement = statements.index(i).field(Fields::finallyBlock); + if (i == 1) { + QVERIFY(!statement); // no finally in last statement + continue; + } + + QVERIFY(statement); + QCOMPARE(statement.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(statement.field(Fields::statements) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"insideFinally"_s); + } + } + + void plainJSDOM_data() + { + QTest::addColumn<QString>("filename"); + QTest::addColumn<QString>("content"); + + QTest::newRow("simplestJSStatement") + << "simplestJSStatement.js" << QString(u"let v=1;\n"_s); + QTest::newRow("import") + << "import.js" + << QString(u".import \"main.js\" as Main\nconsole.log(Main.a);\n"_s); + QTest::newRow("simplestJSmodule") + << "simplestJSmodule.mjs" << QString(u"export function entry() {}\n"_s); + } + + // Verifies that DOM can load .js and .mjs files and + // parse / store the content inside the ScriptExpression + void plainJSDOM() + { + using namespace Qt::StringLiterals; + QFETCH(QString, filename); + QFETCH(QString, content); + + QString testFile = baseDir + "/" + filename; + auto dom = parse(testFile, qmltypeDirs); + QVERIFY(dom); + QCOMPARE(dom.internalKind(), DomType::JsFile); + auto filePtr = dom.fileObject().ownerAs<JsFile>(); + QVERIFY(filePtr && filePtr->isValid()); + auto exprAsString = dom.field(Fields::expression) + .field(Fields::code) + .value() + .toString(); + QVERIFY(!exprAsString.isEmpty()); + exprAsString.replace("\r\n", "\n"); + QCOMPARE(exprAsString, content); + } + +private: + struct DomItemWithLocation + { + DomItem item; + FileLocations::Tree tree; + }; +private slots: + + void fileLocations_data() + { + QTest::addColumn<QString>("fileName"); + QDir dir(baseDir); + for (const QString &file : dir.entryList(QDir::Files, QDir::Name)) { + if (!file.endsWith(".qml")) + continue; + QTest::addRow("%s", file.toStdString().c_str()) << baseDir + QDir::separator() + file; + } + } + + /*! + \internal + Check if finalizeScriptExpression() was called with the correct FileLocations::Tree + argument when this test fails. + */ + void fileLocations() + { + QFETCH(QString, fileName); + + DomItem rootQmlObject = rootQmlObjectFromFile(fileName, qmltypeDirs); + std::deque<DomItemWithLocation> queue; + + DomItem fileDomItem = rootQmlObject.containingFile(); + std::shared_ptr<QmlFile> file = fileDomItem.ownerAs<QmlFile>(); + QVERIFY(file); + + DomItemWithLocation root{ fileDomItem, file->fileLocationsTree() }; + queue.push_back(root); + + while (!queue.empty()) { + DomItemWithLocation current = queue.front(); + queue.pop_front(); + + auto subEls = current.tree->subItems(); + for (auto it = subEls.begin(); it != subEls.end(); ++it) { + DomItem childItem = current.item.path(it.key()); + FileLocations::Tree childTree = + std::static_pointer_cast<AttachedInfoT<FileLocations>>(it.value()); + if (!childItem) { + const auto attachedInfo = FileLocations::findAttachedInfo(current.item); + const DomItem treeItem = current.item.path(attachedInfo.foundTreePath); + qDebug() << current.item.internalKindStr() + << "has incorrect FileLocations! Make sure that " + "finalizeScriptExpression is called with the right arguments. It " + "should print out some debugging information with the " + "qt.qmldom.astcreator.debug logging rule."; + qDebug() << "Current FileLocations has keys:" + << treeItem.field(Fields::subItems).keys() + << "but current Item of type" << current.item.internalKindStr() + << "has fields: " << current.item.fields() + << "and keys: " << current.item.keys() + << "and indexes: " << current.item.indexes(); + } + QVERIFY(childItem.internalKind() != DomType::Empty); + queue.push_back({ childItem, childTree }); + } + } + } + +private slots: + void finalizeScriptExpressions() + { + QString fileName = baseDir + u"/finalizeScriptExpressions.qml"_s; + DomItem rootQmlObject = rootQmlObjectFromFile(fileName, qmltypeDirs); + + /* + Check if the path obtained by the filelocations is the same as the DomItem path. Both + need to be equal to find DomItem's from their location in the source code. + */ + auto compareFileLocationsPathWithCanonicalPath = [](const DomItem &item) { + Path canonical = item.canonicalPath(); + QVERIFY(canonical.length() > 0); + if (canonical.length() > 0) + QCOMPARE(canonical.toString(), + FileLocations::treeOf(item)->canonicalPathForTesting()); + }; + + /* + for all places, where scriptelements are attached to Qml elements in the Dom, check if: + a) scriptelement is accessible from the DomItem (is it correclty attached?) + b) scriptelement has the correct path (is its pathFromOwner the path where it was + attached?) + + For bindings to objects, arrays and scripts, check that the bindingIdentifiers are correctly + attached in the Dom. + */ + + { + DomItem binding = rootQmlObject.field(Fields::bindings).key("binding"); + QCOMPARE(binding.indexes(), 1); + + QCOMPARE(binding.index(0).field(Fields::value).internalKind(), + DomType::ScriptExpression); + QCOMPARE(binding.index(0) + .field(Fields::value) + .field(Fields::scriptElement) + .internalKind(), + DomType::ScriptLiteral); + // Fields::value is in the path of the owner, and therefore should not be in + // pathFromOwner! + DomItem scriptElement = + binding.index(0).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement)); + compareFileLocationsPathWithCanonicalPath(scriptElement); + } + + { + DomItem bindingInPropertyDefinition = + rootQmlObject.field(Fields::bindings).key("bindingInPropertyDefinition"); + QCOMPARE(bindingInPropertyDefinition.indexes(), 1); + + QCOMPARE(bindingInPropertyDefinition.index(0).field(Fields::value).internalKind(), + DomType::ScriptExpression); + QCOMPARE(bindingInPropertyDefinition.index(0) + .field(Fields::value) + .field(Fields::scriptElement) + .internalKind(), + DomType::ScriptLiteral); + // Fields::value is in the path of the owner, and therefore should not be in + // pathFromOwner! + DomItem scriptElement = bindingInPropertyDefinition.index(0) + .field(Fields::value) + .field(Fields::scriptElement); + QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement)); + compareFileLocationsPathWithCanonicalPath(scriptElement); + } + // check the parameters + returnType of the method + { + DomItem return42 = rootQmlObject.field(Fields::methods).key("return42"); + QCOMPARE(return42.indexes(), 1); + + DomItem typeAnnotation = + return42.index(0).field(Fields::returnType).field(Fields::scriptElement); + QCOMPARE(typeAnnotation.internalKind(), DomType::ScriptType); + compareFileLocationsPathWithCanonicalPath(typeAnnotation); + + DomItem parameters = return42.index(0).field(Fields::parameters); + QCOMPARE(parameters.indexes(), 3); + for (int i = 0; i < 3; ++i) { + QCOMPARE(parameters.index(i).field(Fields::defaultValue).internalKind(), + DomType::ScriptExpression); + DomItem scriptElement = + parameters.index(i).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(scriptElement.internalKind(), DomType::ScriptFormalParameter); + QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement)); + compareFileLocationsPathWithCanonicalPath(scriptElement); + } + } + // check the body of the methods + QList<QString> methodNames = { "full", "return42" }; + for (QString &methodName : methodNames) { + DomItem method = rootQmlObject.field(Fields::methods).key(methodName); + DomItem body = method.index(0).field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptExpression); + DomItem scriptElement = body.field(Fields::scriptElement); + QCOMPARE(scriptElement.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement)); + compareFileLocationsPathWithCanonicalPath(scriptElement); + } + + { + DomItem binding = rootQmlObject.field(Fields::bindings).key("arrayBinding"); + QCOMPARE(binding.indexes(), 1); + QCOMPARE(binding.index(0).field(Fields::value).internalKind(), + DomType::ScriptExpression); + QCOMPARE(binding.index(0) + .field(Fields::value) + .field(Fields::scriptElement) + .internalKind(), + DomType::ScriptArray); + // Fields::value is in the path of the owner, and therefore should not be in + // pathFromOwner! + DomItem scriptElement = + binding.index(0).field(Fields::value).field(Fields::scriptElement); + QCOMPARE(scriptElement.pathFromOwner(), Path().field(Fields::scriptElement)); + compareFileLocationsPathWithCanonicalPath(scriptElement); + // also check that the left hand side of the binding is correctly attached to the Dom: + scriptElement = binding.index(0).field(Fields::bindingIdentifiers); + QCOMPARE(scriptElement.pathFromOwner(), + Path::fromString(u".components[\"\"][0].objects[0].bindings[\"arrayBinding\"][" + u"0].bindingIdentifiers")); + compareFileLocationsPathWithCanonicalPath(scriptElement); + } + { + DomItem binding = rootQmlObject.field(Fields::bindings).key("objectBinding"); + QCOMPARE(binding.indexes(), 1); + QCOMPARE(binding.index(0).field(Fields::value).internalKind(), DomType::QmlObject); + // check that the left hand side of the binding is correctly attached to the Dom: + DomItem scriptElement = binding.index(0).field(Fields::bindingIdentifiers); + QCOMPARE(scriptElement.pathFromOwner(), + Path::fromString(u".components[\"\"][0].objects[0].bindings[\"objectBinding\"][" + u"0].bindingIdentifiers")); + compareFileLocationsPathWithCanonicalPath(scriptElement); + } + } + + void goToFile() + { + using namespace Qt::StringLiterals; + const QString filePathA = baseDir + u"/nullStatements.qml"_s; + const QString filePathB = baseDir + u"/propertyBindings.qml"_s; + const QString canonicalFilePathB = QFileInfo(filePathB).canonicalFilePath(); + QVERIFY(!canonicalFilePathB.isEmpty()); + + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + auto envPtr = DomEnvironment::create( + qmltypeDirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, + options); + + DomItem fileA; + DomItem fileB; + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filePathA), + [&fileA](Path, const DomItem &, const DomItem &newIt) { + fileA = newIt.fileObject(); + }); + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filePathB), + [&fileB](Path, const DomItem &, const DomItem &newIt) { + fileB = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + QCOMPARE(fileA.goToFile(canonicalFilePathB), fileB); + } + + void goUp() + { + using namespace Qt::StringLiterals; + const QString filePath = baseDir + u"/nullStatements.qml"_s; + const QString canonicalFilePathB = QFileInfo(filePath).canonicalFilePath(); + QVERIFY(!canonicalFilePathB.isEmpty()); + + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + auto envPtr = DomEnvironment::create( + qmltypeDirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, + options); + + DomItem fileA; + DomItem fileB; + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filePath), + [&fileA](Path, const DomItem &, const DomItem &newIt) { + fileA = newIt.fileObject(); + }); + + envPtr->loadPendingDependencies(); + + QCOMPARE(fileA.top().goUp(1), DomItem()); + QCOMPARE(fileA.top().directParent(), DomItem()); + + DomItem component = fileA.field(Fields::components).key(QString()).index(0); + + DomItem forStatement = component.field(Fields::objects) + .index(0) + .field(Fields::methods) + .key(u"testForNull"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0) + .field(Fields::body); + + DomItem forStatementBlock = forStatement.field(Fields::statements); + + QCOMPARE(forStatementBlock.directParent(), forStatement); + QCOMPARE(forStatementBlock.goUp(1), forStatement); + QCOMPARE(forStatementBlock.goUp(11), component); + + QCOMPARE(forStatement.component(GoTo::Strict), component); + } + +private: + static DomItem parse(const QString &path, const QStringList &qmltypeDirs) + { + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + + auto envPtr = DomEnvironment::create( + qmltypeDirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, + options); + + DomItem fileItem; + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, path), + [&fileItem](Path, const DomItem &, const DomItem &newIt) { + fileItem = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + return fileItem; + } + + static DomItem rootQmlObjectFromFile(const QString &path, const QStringList &qmltypeDirs) + { + auto dom = parse(path, qmltypeDirs); + return dom.rootQmlObject(GoTo::MostLikely); + } + + void fieldMemberExpressionHelper(const DomItem &actual, const QStringList &expected) + { + Q_ASSERT(!expected.isEmpty()); + auto currentString = expected.rbegin(); + auto endString = expected.rend(); + DomItem current = actual; + + for (; currentString != endString; ++currentString, current = current.field(Fields::left)) { + QCOMPARE(current.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(current.field(Fields::operation).value().toInteger(), + ScriptElements::BinaryExpression::FieldMemberAccess); + QCOMPARE(current.field(Fields::right).internalKind(), + DomType::ScriptIdentifierExpression); + QCOMPARE(current.field(Fields::right).field(Fields::identifier).value().toString(), + *currentString); + } + } + +private slots: + void mapsKeyedByFileLocationRegion() + { + using namespace Qt::StringLiterals; + const QString filePath = baseDir + u"/fileLocationRegion.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(filePath, qmltypeDirs); + QVERIFY(rootQmlObject); + + // test if preComments map works correctly with DomItem interface + const DomItem binding = rootQmlObject.field(Fields::bindings).key(u"helloWorld"_s).index(0); + const DomItem bindingRegionComments = + binding.field(Fields::comments).field(Fields::regionComments); + const DomItem preComments = + bindingRegionComments.key(fileLocationRegionName(FileLocationRegion::IdentifierRegion)) + .field(Fields::preComments); + + QCOMPARE(preComments.indexes(), 1); + QString rawPreComment = preComments.index(0).field(Fields::rawComment).value().toString(); + QCOMPARE(preComments.index(0) + .field(Fields::rawComment) + .value() + .toString() + // replace weird newlines by \n + .replace("\r\n", "\n") + .replace("\r", "\n"), + u" // before helloWorld binding\n "_s); + + // test if postComments map works correctly with DomItem interface + const DomItem postComments = + bindingRegionComments + .key(fileLocationRegionName(FileLocationRegion::MainRegion)) + .field(Fields::postComments); + QCOMPARE(postComments.indexes(), 1); + QCOMPARE(postComments.index(0) + .field(Fields::rawComment) + .value() + .toString() + // replace the windows newlines by \n + .replace("\r\n", "\n") + .replace("\r", "\n"), + u" // after helloWorld binding\n"_s); + + const auto fileLocations = FileLocations::findAttachedInfo(binding); + const DomItem bindingFileLocation = + rootQmlObject.path(fileLocations.foundTreePath).field(Fields::infoItem); + + // test if FileLocation Tree map works correctly with DomItem interface + QCOMPARE(bindingFileLocation.field(Fields::fullRegion).value(), + bindingFileLocation.field(Fields::regions) + .key(fileLocationRegionName(FileLocationRegion::MainRegion)) + .value()); + + QCOMPARE(bindingFileLocation.field(Fields::fullRegion).value(), + sourceLocationToQCborValue(fileLocations.foundTree->info().fullRegion)); + + QCOMPARE(bindingFileLocation.field(Fields::regions) + .key(fileLocationRegionName(FileLocationRegion::MainRegion)) + .value(), + sourceLocationToQCborValue(fileLocations.foundTree->info().regions[MainRegion])); + + QCOMPARE(bindingFileLocation.field(Fields::regions) + .key(fileLocationRegionName(FileLocationRegion::ColonTokenRegion)) + .value(), + sourceLocationToQCborValue(fileLocations.foundTree->info().regions[ColonTokenRegion])); + } + + // add qml files here that should not crash the dom construction + void crashes_data() + { + QTest::addColumn<QString>("filePath"); + + QTest::addRow("inactiveVisitorMarkerCrash") + << baseDir + u"/inactiveVisitorMarkerCrash.qml"_s; + + QTest::addRow("templateStrings") + << baseDir + u"/crashes/templateStrings.qml"_s; + + QTest::addRow("lambda") + << baseDir + u"/crashes/lambda.qml"_s; + + QTest::addRow("bracketsInBinding") + << baseDir + u"/crashes/bracketsInBinding.qml"_s; + } + void crashes() + { + QFETCH(QString, filePath); + + const DomItem rootQmlObject = rootQmlObjectFromFile(filePath, qmltypeDirs); + QVERIFY(rootQmlObject); + } + + void continueStatement() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/continueStatement.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + const DomItem block = + rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements"); + + const DomItem firstContinue = block.index(0); + QCOMPARE(firstContinue.internalKind(), DomType::ScriptContinueStatement); + QCOMPARE(firstContinue.field(Fields::label).value().toString("UNEXISTING"), + u"helloWorld"_s); + + const DomItem secondContinue = block.index(1); + QCOMPARE(secondContinue.internalKind(), DomType::ScriptContinueStatement); + QCOMPARE(secondContinue.field(Fields::label).internalKind(), DomType::Empty); + } + + void breakStatement() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/breakStatement.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + const DomItem block = + rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements"); + + const DomItem firstContinue = block.index(0); + QCOMPARE(firstContinue.internalKind(), DomType::ScriptBreakStatement); + QCOMPARE(firstContinue.field(Fields::label).value().toString("UNEXISTING"), + u"helloWorld"_s); + + const DomItem secondContinue = block.index(1); + QCOMPARE(secondContinue.internalKind(), DomType::ScriptBreakStatement); + QCOMPARE(secondContinue.field(Fields::label).internalKind(), DomType::Empty); + } + + void emptyMethodBody() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/emptyMethodBody.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + const DomItem block = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement"); + + QCOMPARE(block.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(block.field(Fields::statements).indexes(), 0); + } + + void commaExpression() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/commaExpression.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + const DomItem commaExpression = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements[0]"); + + QCOMPARE(commaExpression.internalKind(), DomType::ScriptBinaryExpression); + QCOMPARE(commaExpression.field(Fields::right) + .field(Fields::identifier) + .value() + .toString(), + u"c"_s); + QCOMPARE(commaExpression.field(Fields::left) + .field(Fields::right) + .field(Fields::identifier) + .value() + .toString(), + u"b"_s); + QCOMPARE(commaExpression.field(Fields::left) + .field(Fields::left) + .field(Fields::identifier) + .value() + .toString(), + u"a"_s); + } + + void conditionalExpression() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/conditionalExpression.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + const DomItem commaExpression = rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements[0]"); + + QCOMPARE(commaExpression.internalKind(), DomType::ScriptConditionalExpression); + QCOMPARE(commaExpression.field(Fields::condition) + .field(Fields::identifier) + .value() + .toString(), + u"a"_s); + QCOMPARE(commaExpression.field(Fields::consequence) + .field(Fields::identifier) + .value() + .toString(), + u"b"_s); + QCOMPARE(commaExpression.field(Fields::alternative) + .field(Fields::identifier) + .value() + .toString(), + u"c"_s); + } + + void unaryExpression_data() + { + QTest::addColumn<QString>("fileName"); + QTest::addColumn<DomType>("type"); + + const QString folder = baseDir + u"/unaryExpressions/"_s; + + QTest::addRow("minus") << folder + u"unaryMinus.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("plus") << folder + u"unaryPlus.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("tilde") << folder + u"tilde.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("not") << folder + u"not.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("typeof") << folder + u"typeof.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("delete") << folder + u"delete.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("void") << folder + u"void.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("increment") << folder + u"increment.qml"_s << DomType::ScriptUnaryExpression; + QTest::addRow("decrement") << folder + u"decrement.qml"_s << DomType::ScriptUnaryExpression; + + // post stuff + QTest::addRow("postIncrement") + << folder + u"postIncrement.qml"_s << DomType::ScriptPostExpression; + QTest::addRow("postDecrement") + << folder + u"postDecrement.qml"_s << DomType::ScriptPostExpression; + } + + void unaryExpression() + { + using namespace Qt::StringLiterals; + QFETCH(QString, fileName); + QFETCH(DomType, type); + const DomItem rootQmlObject = rootQmlObjectFromFile(fileName, qmltypeDirs); + const DomItem firstStatement = + rootQmlObject.path(".methods[\"f\"][0].body.scriptElement.statements[0]"); + + QCOMPARE(firstStatement.internalKind(), type); + QCOMPARE(firstStatement.field(Fields::expression) + .field(Fields::identifier) + .value() + .toString(), + u"a"_s); + } + + void objectBindings() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/objectBindings.qml"_s; + const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs); + + const DomItem xBinding = rootQmlObject.path(".bindings[\"x\"][0].value"); + QCOMPARE(xBinding.field(Fields::name).value().toString(), u"root.QQ.Drag"); + QCOMPARE(xBinding.field(Fields::nameIdentifiers).internalKind(), + DomType::ScriptType); + QCOMPARE(xBinding.field(Fields::nameIdentifiers).field(Fields::typeName).internalKind(), + DomType::ScriptBinaryExpression); + QCOMPARE(xBinding.field(Fields::nameIdentifiers) + .field(Fields::typeName) + .field(Fields::operation) + .value() + .toInteger(-1), + ScriptElements::BinaryExpression::FieldMemberAccess); + + QCOMPARE(xBinding.field(Fields::nameIdentifiers).field(Fields::typeName).field(Fields::left).internalKind(), + DomType::ScriptBinaryExpression); + QCOMPARE(xBinding.field(Fields::nameIdentifiers) + .field(Fields::typeName) + .field(Fields::left) + .field(Fields::right) + .value() + .toString(), + u"QQ"); + QCOMPARE(xBinding.field(Fields::nameIdentifiers) + .field(Fields::typeName) + .field(Fields::left) + .field(Fields::left) + .value() + .toString(), + u"root"); + + const DomItem item = rootQmlObject.path(".children[0]"); + QCOMPARE(item.field(Fields::nameIdentifiers).field(Fields::typeName).value().toString(), + u"Item"); + + const DomItem qqItem = rootQmlObject.path(".children[1]"); + QCOMPARE(qqItem.field(Fields::nameIdentifiers) + .field(Fields::typeName) + .field(Fields::operation) + .value() + .toInteger(-1), + ScriptElements::BinaryExpression::FieldMemberAccess); + QCOMPARE(qqItem.field(Fields::nameIdentifiers) + .field(Fields::typeName) + .field(Fields::right) + .value() + .toString(), + u"Item"); + QCOMPARE(qqItem.field(Fields::nameIdentifiers) + .field(Fields::typeName) + .field(Fields::left) + .value() + .toString(), + u"QQ"); + } + + void scriptExpression() + { + // verifying support of ECMA script modules by ScriptExpression + const ScriptExpression esmExport("export function a(){}", + ScriptExpression::ExpressionType::ESMCode); + QVERIFY(esmExport.localErrors().empty()); + } + + void semanticAnalysis() + { + + DomItem baseItem; + DomItem derivedItem; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + + auto envPtr = + DomEnvironment::create(qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option{}, options); + + envPtr->loadFile( + FileToLoad::fromFileSystem(envPtr, baseDir + u"/Base.qml"_s), + [&baseItem](Path, const DomItem &, const DomItem &newIt) { + baseItem = newIt.rootQmlObject(GoTo::MostLikely); + }); + + envPtr->loadFile( + FileToLoad::fromFileSystem(envPtr, baseDir + u"/Derived.qml"_s), + [&derivedItem](Path, const DomItem &, const DomItem &newIt) { + derivedItem = newIt.rootQmlObject(GoTo::MostLikely); + }); + envPtr->loadPendingDependencies(); + + const auto baseScope = baseItem.semanticScope(); + const auto derivedScope = derivedItem.semanticScope(); + + QCOMPARE_NE(baseScope, QQmlJSScope::ConstPtr{}); + QCOMPARE(baseScope, derivedScope->baseType()); + } + + void propertyDefinitionScopes() + { + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + + auto envPtr = + DomEnvironment::create(qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option{}, options); + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir + u"/propertyBindings.qml"_s), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.rootQmlObject(GoTo::MostLikely); + }); + envPtr->loadPendingDependencies(); + + { + const auto a = qmlObject.field(Fields::propertyDefs).key(u"a").index(0); + const auto scopeA = a.semanticScope(); + QCOMPARE_NE(scopeA, QQmlJSScope::ConstPtr{}); + QCOMPARE(scopeA->scopeType(), QQmlSA::ScopeType::QMLScope); + } + + { + const auto b = qmlObject.field(Fields::propertyDefs).key(u"b").index(0); + const auto scopeB = b.semanticScope(); + QCOMPARE_NE(scopeB, QQmlJSScope::ConstPtr{}); + QCOMPARE(scopeB->scopeType(), QQmlSA::ScopeType::QMLScope); + } + } + + // simulate qmlls loading the same file twice like in QTBUG-123591 + void loadFileTwice() + { + DomItem qmlObject; + DomItem qmlObject2; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ baseDir + u"/propertyBindings.qml"_s }; + QFile file(fileName); + QVERIFY(file.open(QFile::ReadOnly)); + const QString content = file.readAll(); + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir + u"/propertyBindings.qml"_s), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.rootQmlObject(GoTo::MostLikely); + }); + envPtr->loadPendingDependencies(); + + // should not assert when loading the same file again + auto envPtrChild = envPtr->makeCopy(DomItem(envPtr)); + envPtrChild->loadFile( + FileToLoad::fromMemory(envPtr, baseDir + u"/propertyBindings.qml"_s, content), + [&qmlObject2](Path, const DomItem &, const DomItem &newIt) { + qmlObject2 = newIt.rootQmlObject(GoTo::MostLikely); + }); + envPtrChild->loadPendingDependencies(); + } + + void populateLazyFileBeforeCommitToBase() + { + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) }; + + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + envPtrChild->loadFile( + FileToLoad::fromFileSystem(envPtrChild, fileName), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + + const DomItem childEnv = DomItem(envPtrChild->shared_from_this()); + // populate the lazy file by accessing it via the DomItem interface + const DomItem mainComponent = + childEnv.field(Fields::qmlFileWithPath) + .key(fileName) + .field(Fields::currentItem) + .field(Fields::components) + .key(QString()); + QVERIFY(mainComponent); + + envPtrChild->commitToBase(DomItem(envPtrChild)); + } // destroy the temporary environment that the file was loaded into + + // also make sure that the main component also exists in the base environment after the + // commitToBase call. + const DomItem env = DomItem(envPtr->shared_from_this()); + const DomItem mainComponent = env.field(Fields::qmlFileWithPath) + .key(fileName) + .field(Fields::currentItem) + .field(Fields::components) + .key(QString()); + QVERIFY(mainComponent); + } + + void populateLazyFileAfterCommitToBase() + { + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) }; + + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + envPtrChild->loadFile( + FileToLoad::fromFileSystem(envPtrChild, fileName), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + envPtrChild->commitToBase(DomItem(envPtrChild)); + } // destroy the temporary environment that the file was loaded into + + const DomItem env = DomItem(envPtr->shared_from_this()); + // populate the lazy file by accessing it via the DomItem interface + const DomItem mainComponent = env.field(Fields::qmlFileWithPath) + .key(fileName) + .field(Fields::currentItem) + .field(Fields::components) + .key(QString()); + QVERIFY(mainComponent); + } + + void qtbug_124799() + { + // reproduces the completion crash in QTBUG-124799 that was actually not completion related: + // triggering the completion was triggering the population of a file, that led to a + // heap-use-after-free. The steps to reproduce the crash are following: + // 1. load a file in a temporary environment + // 2. grab an unpopulated qqmljsscope from the type resolver of the loaded file + // 3. destroy the temporary environment + // 4. update the loaded file with new content, to make sure the QQmlJSImporter (used to + // populate of qmlfiles) has no more strong references in the QmlFile. + // 5. populate the unpopulated qqmljsscope: its factory should have kept track that its + // environment is not the temporary one but the base one (because of the commitToBase() + // call) and use the correct QQmlJSImporter (if its the one from the temporary environment + // this will lead to the heap-use-after-free memory error you get when triggering + // completions before this fix) + + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + options.setFlag(DomCreationOption::WithRecovery); + + std::shared_ptr<DomEnvironment> envPtr = DomEnvironment::create( + qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded, options); + + const QString fileName{ QDir::cleanPath(baseDir + u"/propertyBindings.qml"_s) }; + + QQmlJSScope::ConstPtr populateAfterEnvironmentDestruction; + + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + envPtrChild->loadFile( + FileToLoad::fromFileSystem(envPtrChild, fileName), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + + auto qmlFilePtr = qmlObject.ownerAs<QmlFile>(); + auto resolver = qmlFilePtr->typeResolver(); + // simulate completion by grabbing some type from the resolver + populateAfterEnvironmentDestruction = resolver->importedTypes()[u"Derived"_s].scope; + envPtrChild->commitToBase(DomItem(envPtrChild)); + } + + // update the file + { + DomItem envChild = DomItem(envPtr).makeCopy(DomItem::CopyOption::EnvConnected).item(); + auto envPtrChild = envChild.ownerAs<DomEnvironment>(); + + // simulate user typing something + QFile file(fileName); + QVERIFY(file.open(QFile::ReadOnly)); + const QString content = file.readAll(); + const QString newContent = content + "\n // important comment here\n"; + envPtrChild->loadFile(FileToLoad::fromMemory(envPtrChild, fileName, newContent), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.fileObject(); + }); + envPtrChild->loadPendingDependencies(); + envPtrChild->commitToBase(DomItem(envPtrChild)); + } + + // step 3: populate the lazy qqmljsscope, it should not crash + QCOMPARE(populateAfterEnvironmentDestruction->filePath(), + QDir::cleanPath(baseDir + u"/Derived.qml"_s)); + } + + void visitTreeFilter() + { + DomItem qmlObject; + DomCreationOptions options; + options.setFlag(DomCreationOption::WithScriptExpressions); + options.setFlag(DomCreationOption::WithSemanticAnalysis); + + auto envPtr = + DomEnvironment::create(qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option{}, options); + + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir + u"/visitTreeFilter.qml"_s), + [&qmlObject](Path, const DomItem &, const DomItem &newIt) { + qmlObject = newIt.rootQmlObject(GoTo::MostLikely); + }); + envPtr->loadPendingDependencies(); + + FieldFilter filter({}, { { QString(), QString::fromUtf16(Fields::propertyDefs) } }); + + // check if propertyDefs is visited without the filter + bool success = false; + qmlObject.visitTree( + Path(), emptyChildrenVisitor, VisitOption::Recurse | VisitOption::VisitSelf, + [&success](const Path &p, const DomItem &, bool) { + const QString pathString = p.toString(); + if (p && p.checkHeadName(Fields::propertyDefs)) { + success = true; + } + return true; + }, + emptyChildrenVisitor); + QVERIFY(success); + + // check that propertyDefs is not visited with the filter + success = true; + qmlObject.visitTree( + Path(), emptyChildrenVisitor, VisitOption::Recurse | VisitOption::VisitSelf, + [&success](const Path &p, const DomItem &, bool) { + if (p && p.checkHeadName(Fields::propertyDefs)) { + qWarning() << "Filter did not filter propertyDefs at path" << p; + success = false; + } + return true; + }, + emptyChildrenVisitor, filter); + QVERIFY(success); + } + + void fileLocationRegions_data() + { + QTest::addColumn<QString>("filePath"); + QTest::addColumn<FileLocationRegion>("region"); + QTest::addColumn<QSet<QQmlJS::SourceLocation>>("expectedLocs"); + + QTest::newRow("import") << baseDir + u"/fileLocationRegions/imports.qml"_s << ImportTokenRegion << + QSet { + QQmlJS::SourceLocation{112, 6, 4, 1}, + QQmlJS::SourceLocation{127, 6, 5, 1}, + }; + QTest::newRow("importUri") << baseDir + u"/fileLocationRegions/imports.qml"_s << ImportUriRegion << + QSet { + QQmlJS::SourceLocation{119, 7, 4, 8}, + QQmlJS::SourceLocation{152, 16, 6, 8}, + QQmlJS::SourceLocation{186, 9, 7, 8} + }; + QTest::newRow("asToken") << baseDir + u"/fileLocationRegions/imports.qml"_s << AsTokenRegion << + QSet { + QQmlJS::SourceLocation{169, 2, 6, 25} + }; + QTest::newRow("version") << baseDir + u"/fileLocationRegions/imports.qml"_s << VersionRegion << + QSet { + QQmlJS::SourceLocation{140, 4, 5, 14} + }; + QTest::newRow("namespace") << baseDir + u"/fileLocationRegions/imports.qml"_s << IdNameRegion << + QSet { + QQmlJS::SourceLocation{172, 6, 6, 28} + }; + + QTest::newRow("function") << baseDir + u"/fileLocationRegions/functions.qml"_s + << FunctionKeywordRegion + << QSet{ QQmlJS::SourceLocation{ 139, 9, 7, 5 }, + QQmlJS::SourceLocation{ 195, 9, 10, 9 } }; + + QTest::newRow("signal") << baseDir + u"/fileLocationRegions/functions.qml"_s + << SignalKeywordRegion + << QSet{ QQmlJS::SourceLocation{ 234, 6, 13, 5 }, + QQmlJS::SourceLocation{ 254, 6, 14, 5 } }; + QTest::newRow("return-type-identifier") + << baseDir + u"/fileLocationRegions/functions.qml"_s << TypeIdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 154, 3, 7, 20 }, + QQmlJS::SourceLocation{ 216, 3, 10, 30 } }; + QTest::newRow("function-parameter-type-identifier") + << baseDir + u"/fileLocationRegions/functions.qml"_s << TypeIdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 209, 3, 10, 23 } }; + QTest::newRow("signal-parameter-type-identifier") + << baseDir + u"/fileLocationRegions/functions.qml"_s << TypeIdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 243, 3, 13, 14 }, + QQmlJS::SourceLocation{ 267, 3, 14, 18 } }; + QTest::newRow("signal-parameter-identifier") + << baseDir + u"/fileLocationRegions/functions.qml"_s << IdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 247, 1, 13, 18 }, + QQmlJS::SourceLocation{ 264, 1, 14, 15 } }; + + QTest::newRow("pragma-keyword") + << baseDir + u"/fileLocationRegions/pragmas.qml"_s << PragmaKeywordRegion + << QSet{ QQmlJS::SourceLocation{ 112, 6, 4, 1 }, + QQmlJS::SourceLocation{ 129, 6, 5, 1 }, + QQmlJS::SourceLocation{ 161, 6, 6, 1 }, + QQmlJS::SourceLocation{ 204, 6, 7, 1 }}; + QTest::newRow("pragmaId") + << baseDir + u"/fileLocationRegions/pragmas.qml"_s << IdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 119, 9, 4, 8 }, + QQmlJS::SourceLocation{ 136, 17, 5, 8 }, + QQmlJS::SourceLocation{ 168, 25, 6, 8 }, + QQmlJS::SourceLocation{ 211, 17, 7, 8 }}; + QTest::newRow("pragmaValues") + << baseDir + u"/fileLocationRegions/pragmas.qml"_s << PragmaValuesRegion + << QSet{ QQmlJS::SourceLocation{ 155, 5, 5, 27 }, + QQmlJS::SourceLocation{ 195, 8, 6, 35 }, + QQmlJS::SourceLocation{ 230, 4, 7, 27 }, + QQmlJS::SourceLocation{ 235, 11, 7, 32 }}; + + QTest::newRow("enum-keyword") + << baseDir + u"/fileLocationRegions/enums.qml"_s << EnumKeywordRegion + << QSet{ QQmlJS::SourceLocation{ 139, 4, 7, 5 }}; + QTest::newRow("enum-id") + << baseDir + u"/fileLocationRegions/enums.qml"_s << IdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 144, 3, 7, 10 }}; + QTest::newRow("enum-member") + << baseDir + u"/fileLocationRegions/enums.qml"_s << IdentifierRegion + << QSet{ QQmlJS::SourceLocation{ 158, 3, 8, 9 }, + QQmlJS::SourceLocation{ 175, 3, 9, 9 }, + QQmlJS::SourceLocation{ 188, 3, 10, 9 }}; + QTest::newRow("enum-value") + << baseDir + u"/fileLocationRegions/enums.qml"_s << EnumValueRegion + << QSet{ QQmlJS::SourceLocation{ 164, 1, 8, 15 }, + QQmlJS::SourceLocation{ 194, 2, 10, 15 }}; + } + + void fileLocationRegions() + { + QFETCH(QString, filePath); + QFETCH(FileLocationRegion, region); + QFETCH(QSet<QQmlJS::SourceLocation>, expectedLocs); + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + QFile f(filePath); + QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text)); + QString code = f.readAll(); + DomItem file; + envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code), + [&file](Path, const DomItem &, const DomItem &newIt) { + file = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + const auto tree = FileLocations::treeOf(file); + using AttachedInfo = AttachedInfoT<FileLocations>; + QSet<QQmlJS::SourceLocation> locs; + auto visitor = [&](const Path ¤tPath, const AttachedInfo::Ptr &attachedInfo){ + Q_UNUSED(currentPath); + const auto regions = attachedInfo->info().regions; + if (regions.contains(region)) { + locs << regions.value(region); + } + return true; + }; + AttachedInfo::visitTree(tree, visitor, Path()); + [&] { + QVERIFY(locs.contains(expectedLocs)); + }(); + + if (QTest::currentTestFailed()) { + qDebug() << "Got:\n"; + for (auto &x : locs) { + qDebug() << "Offset: " << x.offset + << ", Length:" << x.length + << ", Startline: " << x.startLine + << ", StartColumn: " << x.startColumn; + } + qDebug() << "But expected: \n"; + for (auto &x : expectedLocs) { + qDebug() << "Offset: " << x.offset + << ", Length:" << x.length + << ", Startline: " << x.startLine + << ", StartColumn: " << x.startColumn; + } + } + } + + void doNotCrashAtAstComments() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/astComments.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem astComments = fileObject.path(".astComments"); + + // Visiting astComment element shouldn't fail + QSet<QStringView> comments; + astComments.visitTree( + Path(), + [&comments](const Path &, const DomItem &item, bool) { + if (item.internalKind() == DomType::Comment) { + auto comment = item.as<Comment>(); + comments << comment->rawComment(); + } + return true; + } + ); + + QVERIFY(comments.contains(u"/*Ast Comment*/ "_s)); + } + + void doNotCrashOnMissingLogger() + { + using namespace Qt::StringLiterals; + const QString testFile = QDir::cleanPath(baseDir + u"/astComments.qml"_s); + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + auto filePtr = fileObject.as<QmlFile>(); + QVERIFY(filePtr); + auto typeResolver = filePtr->typeResolver(); + QVERIFY(typeResolver); + auto logger = typeResolver->logger(); + // make sure that the logger is not use-after-free by checking its content + QCOMPARE(logger->fileName(), testFile); + } + + void commentLocations() + { + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + const auto filePath = baseDir + u"/fileLocationRegions/comments.qml"_s; + QFile f(filePath); + QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text)); + QString code = f.readAll(); + DomItem file; + envPtr->loadFile(FileToLoad::fromMemory(envPtr, filePath, code), + [&file](Path, const DomItem &, const DomItem &newIt) { + file = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + const auto expctedCommentLocations = QSet { + QQmlJS::SourceLocation(0, 41, 1, 1), + QQmlJS::SourceLocation(42,68, 2, 1), + QQmlJS::SourceLocation(126,25, 6, 1), + QQmlJS::SourceLocation(152,14, 10, 1), + QQmlJS::SourceLocation(167,21, 11, 1) + }; + + QSet<SourceLocation> locs; + file.fileObject(GoTo::MostLikely).visitTree(Path(), [&locs](Path, const DomItem &item, bool){ + if (item.internalKind() == DomType::Comment) { + const auto comment = item.as<Comment>(); + if (comment) { + locs << comment->info().sourceLocation(); + } + } + return true; + }, VisitOption::Default, emptyChildrenVisitor, emptyChildrenVisitor); + + + QCOMPARE(locs, expctedCommentLocations); + } + + void lambdas() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem lambda = mainObject.field(Fields::methods) + .key(u"method"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(lambda); + QCOMPARE(lambda.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(lambda.field(Fields::name).value().toString(), u"myLambda"_s); + QCOMPARE(lambda.field(Fields::parameters).indexes(), 2); + QCOMPARE(lambda.field(Fields::parameters).index(0).field(Fields::identifier).value().toString(), u"a"); + QCOMPARE(lambda.field(Fields::parameters).index(1).field(Fields::identifier).value().toString(), u"b"); + + auto scope = lambda.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"b"_s)); + + const DomItem body = lambda.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + } + } + void arrow() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem arrow = mainObject.field(Fields::methods) + .key(u"method"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(2) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(arrow); + QCOMPARE(arrow.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(arrow.field(Fields::name).value().toString(), u"myArrow"_s); + QCOMPARE(arrow.field(Fields::parameters).indexes(), 2); + QCOMPARE(arrow.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"v"); + QCOMPARE(arrow.field(Fields::parameters) + .index(1) + .field(Fields::identifier) + .value() + .toString(), + u"w"); + + auto scope = arrow.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"w"_s)); + + const DomItem body = arrow.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + QCOMPARE(body.field(Fields::statements).indexes(), 1); + QCOMPARE(body.field(Fields::statements).index(0).internalKind(), + DomType::ScriptReturnStatement); + } + } + void lamdbaInBinding() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem lambda = mainObject.field(Fields::bindings) + .key(u"onHelloSignal"_s) + .index(0) + .field(Fields::value) + .field(Fields::scriptElement); + QVERIFY(lambda); + QCOMPARE(lambda.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(lambda.field(Fields::name).value().toString(), QString()); + QCOMPARE(lambda.field(Fields::parameters).indexes(), 3); + QCOMPARE(lambda.field(Fields::parameters).index(0).field(Fields::identifier).value().toString(), u"x"); + QCOMPARE(lambda.field(Fields::parameters).index(2).field(Fields::identifier).value().toString(), u"z"); + auto scope = lambda.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"z"_s)); + const DomItem body = lambda.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + } + } + void nestedFunction() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem nested = mainObject.field(Fields::methods) + .key(u"testNestedFunctions"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0); + QVERIFY(nested); + QCOMPARE(nested.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(nested.field(Fields::name).value().toString(), u"nested"_s); + QCOMPARE(nested.field(Fields::parameters).indexes(), 3); + QCOMPARE(nested.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"tic"); + QCOMPARE(nested.field(Fields::parameters) + .index(2) + .field(Fields::identifier) + .value() + .toString(), + u"toe"); + const DomItem body = nested.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto scope = nested.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"toe"_s)); + } + } + void generatorDeclaration() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem generator = mainObject.field(Fields::methods) + .key(u"generators"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0); + QVERIFY(generator); + QCOMPARE(generator.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(generator.field(Fields::name).value().toString(), u"myGeneratorDeclaration"_s); + QCOMPARE(generator.field(Fields::parameters).indexes(), 2); + QCOMPARE(generator.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"a"); + QCOMPARE(generator.field(Fields::parameters) + .index(1) + .field(Fields::identifier) + .value() + .toString(), + u"b"); + const DomItem body = generator.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto scope = generator.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"b"_s)); + + const DomItem yieldExpression = + generator.field(Fields::body).field(Fields::statements).index(0); + QCOMPARE(yieldExpression.internalKind(), DomType::ScriptYieldExpression); + QCOMPARE(yieldExpression.field(Fields::expression).value().toInteger(), 5); + } + } + void generatorExpression() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem mainObject = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0); + { + const DomItem generator = mainObject.field(Fields::methods) + .key(u"generators"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(1) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(generator); + QCOMPARE(generator.internalKind(), DomType::ScriptFunctionExpression); + QCOMPARE(generator.field(Fields::name).value().toString(), u"myGenerator"_s); + QCOMPARE(generator.field(Fields::parameters).indexes(), 3); + QCOMPARE(generator.field(Fields::parameters) + .index(0) + .field(Fields::identifier) + .value() + .toString(), + u"tic"); + QCOMPARE(generator.field(Fields::parameters) + .index(2) + .field(Fields::identifier) + .value() + .toString(), + u"toe"); + const DomItem body = generator.field(Fields::body); + QCOMPARE(body.internalKind(), DomType::ScriptBlockStatement); + auto scope = generator.semanticScope(); + QVERIFY(scope); + QVERIFY(scope->jsIdentifier(u"toe"_s)); + } + } + void generatorDeclarationInQmlObject() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem statements = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0) + .field(Fields::methods) + .key(u"generatorInQmlObject"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements); + { + const DomItem nested = statements.index(0); + QVERIFY(nested); + QCOMPARE(nested.internalKind(), DomType::ScriptFunctionExpression); + + const DomItem nested2 = statements.index(1); + QVERIFY(nested2); + QCOMPARE(nested2.internalKind(), DomType::ScriptFunctionExpression); + + const DomItem yield = statements.index(2); + QVERIFY(yield); + QCOMPARE(yield.internalKind(), DomType::ScriptYieldExpression); + + const DomItem yieldStar = statements.index(3); + QVERIFY(yieldStar); + QCOMPARE(yieldStar.internalKind(), DomType::ScriptYieldExpression); + + } + } + void traditionalLambda() + { + using namespace Qt::StringLiterals; + const QString testFile = baseDir + u"/lambdas.qml"_s; + const DomItem fileObject = rootQmlObjectFromFile(testFile, qmltypeDirs).fileObject(); + const DomItem initializer = fileObject.field(Fields::components) + .key(QString()) + .index(0) + .field(Fields::objects) + .index(0) + .field(Fields::methods) + .key(u"traditionalLambda"_s) + .index(0) + .field(Fields::body) + .field(Fields::scriptElement) + .field(Fields::statements) + .index(0) + .field(Fields::declarations) + .index(0) + .field(Fields::initializer); + QVERIFY(initializer); + QCOMPARE(initializer.internalKind(), DomType::ScriptParenthesizedExpression); + const DomItem lambda = initializer.field(Fields::expression); + QVERIFY(lambda); + QCOMPARE(lambda.internalKind(), DomType::ScriptFunctionExpression); + } + + private: QString baseDir; QStringList qmltypeDirs; diff --git a/tests/auto/qmldom/errormessage/CMakeLists.txt b/tests/auto/qmldom/errormessage/CMakeLists.txt index 5a61ebcb29..7c82876827 100644 --- a/tests/auto/qmldom/errormessage/CMakeLists.txt +++ b/tests/auto/qmldom/errormessage/CMakeLists.txt @@ -7,6 +7,12 @@ ## tst_qmldomerrormessage Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmldomerrormessage LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qmldomerrormessage SOURCES tst_qmldomerrormessage.cpp tst_qmldomerrormessage.h diff --git a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp index 0ca1091a18..2c2007c119 100644 --- a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp +++ b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_qmldomerrormessage.h" #include <QtQmlDom/private/qqmldomerrormessage_p.h> diff --git a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h index f7d3a291b6..31628ef52d 100644 --- a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h +++ b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtQmlDom/qqmldom_global.h> #include <QtTest/QtTest> diff --git a/tests/auto/qmldom/merging/CMakeLists.txt b/tests/auto/qmldom/merging/CMakeLists.txt index 2eaf7b9615..a33df96216 100644 --- a/tests/auto/qmldom/merging/CMakeLists.txt +++ b/tests/auto/qmldom/merging/CMakeLists.txt @@ -6,6 +6,13 @@ ##################################################################### ## tst_qmldomitem Binary: ##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_dommerging LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. diff --git a/tests/auto/qmldom/merging/tst_dommerging.cpp b/tests/auto/qmldom/merging/tst_dommerging.cpp index 2dc1570e32..1fa994b1a3 100644 --- a/tests/auto/qmldom/merging/tst_dommerging.cpp +++ b/tests/auto/qmldom/merging/tst_dommerging.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_dommerging.h" QTEST_MAIN(QQmlJS::Dom::TestDomMerging) diff --git a/tests/auto/qmldom/merging/tst_dommerging.h b/tests/auto/qmldom/merging/tst_dommerging.h index 01d20014bf..59937b279e 100644 --- a/tests/auto/qmldom/merging/tst_dommerging.h +++ b/tests/auto/qmldom/merging/tst_dommerging.h @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TST_DOMMERGING_H #define TST_DOMMERGING_H @@ -38,16 +38,13 @@ private slots: auto envPtr = std::shared_ptr<QQmlJS::Dom::DomEnvironment>(new QQmlJS::Dom::DomEnvironment( qmltypeDirs, DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies)); - QQmlJS::Dom::DomItem env(envPtr); - QVERIFY(env); QString testFile1 = baseDir + QLatin1String("/test1.qml"); - env.loadFile( - testFile1, QString(), - [this](Path, const DomItem &, const DomItem &newIt) { this->tFile = newIt; }, - LoadOption::DefaultLoad); - env.loadFile(baseDir, QString(), {}, LoadOption::DefaultLoad); - envPtr->loadPendingDependencies(env); + envPtr->loadFile( + FileToLoad::fromFileSystem(envPtr, testFile1), + [this](Path, const DomItem &, const DomItem &newIt) { this->tFile = newIt; }); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, baseDir), {}); + envPtr->loadPendingDependencies(); QVERIFY(tFile); tFile = tFile.field(Fields::currentItem); diff --git a/tests/auto/qmldom/path/CMakeLists.txt b/tests/auto/qmldom/path/CMakeLists.txt index dbffe714d9..bf3e5d20f5 100644 --- a/tests/auto/qmldom/path/CMakeLists.txt +++ b/tests/auto/qmldom/path/CMakeLists.txt @@ -7,6 +7,12 @@ ## tst_qmldompath Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmldompath LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qmldompath SOURCES tst_qmldompath.cpp tst_qmldompath.h diff --git a/tests/auto/qmldom/path/tst_qmldompath.cpp b/tests/auto/qmldom/path/tst_qmldompath.cpp index 86ef15e035..54a634e476 100644 --- a/tests/auto/qmldom/path/tst_qmldompath.cpp +++ b/tests/auto/qmldom/path/tst_qmldompath.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_qmldompath.h" QTEST_MAIN(QQmlJS::Dom::PathEls::TestPaths) diff --git a/tests/auto/qmldom/path/tst_qmldompath.h b/tests/auto/qmldom/path/tst_qmldompath.h index 24d8c30bcf..f463b93164 100644 --- a/tests/auto/qmldom/path/tst_qmldompath.h +++ b/tests/auto/qmldom/path/tst_qmldompath.h @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TST_QMLDOMPATH_H #define TST_QMLDOMPATH_H @@ -16,7 +16,7 @@ namespace PathEls { class TestPaths: public QObject { Q_OBJECT public: - void testPathInternals(Path p1) + void testPathInternals(const Path &p1) { QCOMPARE(p1.component(0).kind(), Kind::Root); QCOMPARE(p1.component(1).kind(), Kind::Current); @@ -85,11 +85,11 @@ private slots: auto c10=PathComponent(Any()); QCOMPARE(c10.kind(), Kind::Any); QVERIFY(c9!=c10); - auto c11=PathComponent(Filter([](DomItem){ return true; })); + auto c11=PathComponent(Filter([](const DomItem &){ return true; })); auto c12=c11; - auto c13=PathComponent(Filter([](DomItem){ return false; })); - auto c14=PathComponent(Filter([](DomItem){ return false; }, u"skipAll")); - auto c15=PathComponent(Filter([](DomItem){ return true; }, u"skipAll")); + auto c13=PathComponent(Filter([](const DomItem &){ return false; })); + auto c14=PathComponent(Filter([](const DomItem &){ return false; }, u"skipAll")); + auto c15=PathComponent(Filter([](const DomItem &){ return true; }, u"skipAll")); QCOMPARE(c11.kind(), Kind::Filter); QCOMPARE(c11, c11); QVERIFY(c11 != c12); // native code assumed to be non comparable and different even if they are the same @@ -132,7 +132,7 @@ private slots: QCOMPARE(p6[6].headKind(), Kind::Empty); auto rString = u"$.@.aa[4][\"bla\"][*]."; QCOMPARE(p6.toString(), rString); - auto p7 = p6.filter([](DomItem){ return true; }, u"true"); + auto p7 = p6.filter([](const DomItem &){ return true; }, u"true"); auto p7Str = p7.toString(); QCOMPARE(p7Str, u"$.@.aa[4][\"bla\"][*].[?(true)]"); auto p8 = p7.dropTail(); @@ -161,7 +161,7 @@ private slots: void testPathSplit() { - QList<Path> paths({Path(), + const QList<Path> paths({Path(), Path::Root(PathRoot::Env).field(u"pippo").key(u"pluto").index(4), Path::Root(PathRoot::Env).field(u"pippo").key(u"pluto"), Path::Root(PathRoot::Env).field(u"pippo"), @@ -175,7 +175,7 @@ private slots: Path::Index(4), Path::Key(u"zz") }); - foreach (Path p, paths) { + for (const Path &p : paths) { Source s = p.split(); QCOMPARE(p, s.pathToSource.path(s.pathFromSource)); if (!s.pathFromSource) diff --git a/tests/auto/qmldom/reformatter/CMakeLists.txt b/tests/auto/qmldom/reformatter/CMakeLists.txt index 7b7f7e4708..1b8cfb0d8a 100644 --- a/tests/auto/qmldom/reformatter/CMakeLists.txt +++ b/tests/auto/qmldom/reformatter/CMakeLists.txt @@ -4,6 +4,13 @@ ##################################################################### ## tst_reformatter: ##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_reformatter LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. diff --git a/tests/auto/qmldom/reformatter/tst_reformatter.cpp b/tests/auto/qmldom/reformatter/tst_reformatter.cpp index 75f8b445e7..00d26bc55b 100644 --- a/tests/auto/qmldom/reformatter/tst_reformatter.cpp +++ b/tests/auto/qmldom/reformatter/tst_reformatter.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_reformatter.h" QTEST_MAIN(QQmlJS::Dom::TestReformatter) diff --git a/tests/auto/qmldom/reformatter/tst_reformatter.h b/tests/auto/qmldom/reformatter/tst_reformatter.h index 908c2f66a9..5cf800c80c 100644 --- a/tests/auto/qmldom/reformatter/tst_reformatter.h +++ b/tests/auto/qmldom/reformatter/tst_reformatter.h @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TST_QMLDOMCODEFORMATTER_H #define TST_QMLDOMCODEFORMATTER_H @@ -8,6 +8,7 @@ #include <QtQmlDom/private/qqmldomoutwriter_p.h> #include <QtQmlDom/private/qqmldomitem_p.h> #include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQmlDom/private/qqmldomreformatter_p.h> #include <QtTest/QtTest> #include <QCborValue> @@ -24,6 +25,69 @@ class TestReformatter : public QObject { Q_OBJECT public: +private: + // TODO Move to a dedicated LineWriter factory / LineWriter API ? + enum class LineWriterType { Default, Indenting }; + std::unique_ptr<LineWriter> getLineWriter(const SinkF &innerSink, + const LineWriterOptions &lwOptions) + { + return lwOptions.maxLineLength > 0 + ? getLineWriter(LineWriterType::Indenting, innerSink, lwOptions) + : getLineWriter(LineWriterType::Default, innerSink, lwOptions); + } + + std::unique_ptr<LineWriter> getLineWriter(LineWriterType type, const SinkF &innerSink, + const LineWriterOptions &lwOptions) + { + switch (type) { + case LineWriterType::Indenting: + return std::make_unique<IndentingLineWriter>(innerSink, QLatin1String("*testStream*"), + lwOptions); + default: + return std::make_unique<LineWriter>(innerSink, QLatin1String("*testStream*"), + lwOptions); + } + Q_UNREACHABLE_RETURN(nullptr); + } + + // "Unix" LineWriter (with '\n' line endings) is used by default, + // under the assumption that line endings are properly tested in lineWriter() test. + static LineWriterOptions defaultLineWriterOptions() + { + LineWriterOptions opts; + opts.lineEndings = LineWriterOptions::LineEndings::Unix; + return opts; + } + + QString formatJSCode(const QString &jsCode, + const LineWriterOptions &lwOptions = defaultLineWriterOptions()) + { + return formatPlainJS(jsCode, ScriptExpression::ExpressionType::JSCode, lwOptions); + } + + QString formatJSModuleCode(const QString &jsCode, + const LineWriterOptions &lwOptions = defaultLineWriterOptions()) + { + return formatPlainJS(jsCode, ScriptExpression::ExpressionType::ESMCode, lwOptions); + } + + QString formatPlainJS(const QString &jsCode, ScriptExpression::ExpressionType exprType, + const LineWriterOptions &lwOptions = defaultLineWriterOptions()) + { + QString resultStr; + QTextStream res(&resultStr); + auto lwPtr = getLineWriter([&res](QStringView s) { res << s; }, lwOptions); + assert(lwPtr); + OutWriter ow(*lwPtr); + + const ScriptExpression scriptItem(jsCode, exprType); + scriptItem.writeOut(DomItem(), ow); + + lwPtr->flush(); // flush instead of eof to ignore line endings + res.flush(); + return resultStr; + } + private slots: void reindent_data() { @@ -145,18 +209,16 @@ private slots: QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter"); QStringList qmltypeDirs = QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); - DomItem env = DomEnvironment::create( + auto envPtr = DomEnvironment::create( qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); QString testFilePath = baseDir + QLatin1Char('/') + inFile; DomItem tFile; - env.loadBuiltins(); - env.loadFile( - testFilePath, QString(), - [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, - LoadOption::DefaultLoad); - env.loadPendingDependencies(); + envPtr->loadBuiltins(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFilePath), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }); + envPtr->loadPendingDependencies(); MutableDomItem myFile = tFile.field(Fields::currentItem); @@ -252,20 +314,16 @@ private slots: QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter"); QStringList qmltypeDirs = QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); - DomItem env = DomEnvironment::create( + auto envPtr = DomEnvironment::create( qmltypeDirs, QQmlJS::Dom::DomEnvironment::Option::SingleThreaded | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); QString testFilePath = baseDir + QLatin1Char('/') + inFile; DomItem tFile; - env.loadBuiltins(); - env.loadFile( - testFilePath, QString(), - [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }, - LoadOption::DefaultLoad); - env.loadPendingDependencies(); - - MutableDomItem myFile = tFile.field(Fields::currentItem); + envPtr->loadBuiltins(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFilePath), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }); + envPtr->loadPendingDependencies(); QString resultStr; QTextStream res(&resultStr); @@ -388,6 +446,331 @@ private slots: } } + void hoistableDeclaration_data() + { + QTest::addColumn<QString>("declarationToBeFormatted"); + QTest::addColumn<QString>("expectedFormattedDeclaration"); + + QTest::newRow("Function") << QStringLiteral(u"function a(a,b){}") + << QStringLiteral(u"function a(a, b) {}"); + QTest::newRow("AnonymousFunction") << QStringLiteral(u"let f=function (a,b){}") + << QStringLiteral(u"let f = function (a, b) {}"); + QTest::newRow("Generator_lhs_star") + << QStringLiteral(u"function* g(a,b){}") << QStringLiteral(u"function* g(a, b) {}"); + QTest::newRow("Generator_rhs_star") + << QStringLiteral(u"function *g(a,b){}") << QStringLiteral(u"function* g(a, b) {}"); + QTest::newRow("AnonymousGenerator") << QStringLiteral(u"let g=function * (a,b){}") + << QStringLiteral(u"let g = function* (a, b) {}"); + QTest::newRow("yield") << QStringLiteral(u"let g=function*(a,b){yield a;}") + << QStringLiteral(u"let g = function* (a, b) {\nyield a;\n}"); + QTest::newRow("yield*") << QStringLiteral(u"let g=function*(a,b){yield*a;}") + << QStringLiteral(u"let g = function* (a, b) {\nyield* a;\n}"); + QTest::newRow("yield*NoSemicolon") + << QStringLiteral(u"let g=function*(a,b){yield*a}") + << QStringLiteral(u"let g = function* (a, b) {\nyield* a;\n}"); + } + + // https://262.ecma-international.org/7.0/#prod-HoistableDeclaration + void hoistableDeclaration() + { + QFETCH(QString, declarationToBeFormatted); + QFETCH(QString, expectedFormattedDeclaration); + + QString formattedDeclaration = formatJSCode(declarationToBeFormatted); + + QCOMPARE(formattedDeclaration, expectedFormattedDeclaration); + } + + void exportDeclarations_data() + { + QTest::addColumn<QString>("exportToBeFormatted"); + QTest::addColumn<QString>("expectedFormattedExport"); + // not exhaustive list of ExportDeclarations as per + // https://262.ecma-international.org/7.0/#prod-ExportDeclaration + + // LexicalDeclaration + QTest::newRow("LexicalDeclaration_let_Binding") + << QStringLiteral(u"export let name") << QStringLiteral(u"export let name;"); + QTest::newRow("LexicalDeclaration_const_BindingList") + << QStringLiteral(u"export const " + u"n1=1,n2=2,n3=3,n4=4,n5=5") + << QStringLiteral(u"export const " + u"n1 = 1, n2 = 2, n3 = 3, n4 = 4, n5 = 5;"); + QTest::newRow("LexicalDeclaration_const_ArrayBinding") + << QStringLiteral(u"export const " + u"[a,b]=a_and_b") + << QStringLiteral(u"export const " + u"[a, b] = a_and_b;"); + QTest::newRow("LexicalDeclaration_let_ObjectBinding") + << QStringLiteral(u"export let " + u"{a,b:c}=a_and_b") + << QStringLiteral(u"export let " + u"{\na,\nb: c\n} = a_and_b;"); + + // ClassDeclaration + QTest::newRow("ClassDeclaration") << QStringLiteral(u"export " + u"class A extends B{}") + << QStringLiteral(u"export " + u"class A extends B {}"); + + // HoistableDeclaration + QTest::newRow("HoistableDeclaration_FunctionDeclaration") + << QStringLiteral(u"export " + u"function a(a,b){}") + << QStringLiteral(u"export " + u"function a(a, b) {}"); + QTest::newRow("HoistableDeclaration_GeneratorDeclaration") + << QStringLiteral(u"export " + u"function * g(a,b){}") + << QStringLiteral(u"export " + u"function* g(a, b) {}"); + + // export ExportClause ; + QTest::newRow("ExportClause_Empty") + << QStringLiteral(u"export{}") << QStringLiteral(u"export {};"); + QTest::newRow("ExportClause_1Specifier") + << QStringLiteral(u"export{one}") << QStringLiteral(u"export { one };"); + QTest::newRow("ExportClause_Specifier_as") + << QStringLiteral(u"export{one as o}") << QStringLiteral(u"export { one as o };"); + QTest::newRow("ExportClause_Specifier_as_StringLiteral") + << QStringLiteral(u"export{one as \"s\"}") + << QStringLiteral(u"export { one as \"s\" };"); + QTest::newRow("ExportClause_ExportsList") + << QStringLiteral(u"export{one,two,three,four as fo,five}") + << QStringLiteral(u"export { one, two, three, four as fo, five };"); + + // export * FromClause ; + QTest::newRow("star") << QStringLiteral(u"export * from \"design\"") + << QStringLiteral(u"export * from \"design\";"); + QTest::newRow("star_as_Specifier") << QStringLiteral(u"export * as star from \"design\"") + << QStringLiteral(u"export * as star from \"design\";"); + + // export ExportClause FromClause ; + QTest::newRow("ExportClause") + << QStringLiteral(u"export {i1 as n1,i2 as n2,nN} from \"M\"") + << QStringLiteral(u"export { i1 as n1, i2 as n2, nN } from \"M\";"); + + // export default HoistableDeclaration + QTest::newRow("Default_AnonymousFunction") + << QStringLiteral(u"export default function(a,b){}") + << QStringLiteral(u"export default function (a, b) {}"); + QTest::newRow("Default_AnonymousGenerator") + << QStringLiteral(u"export default function * (a,b){}") + << QStringLiteral(u"export default function* (a, b) {}"); + QTest::newRow("Default_Function") << QStringLiteral(u"export default function a(a,b){}") + << QStringLiteral(u"export default function a(a, b) {}"); + + // export default ClassDeclaration + QTest::newRow("Default_Class") << QStringLiteral(u"export default class A{}") + << QStringLiteral(u"export default class A {}"); + QTest::newRow("Default_AnonymousClass") + << QStringLiteral(u"export default class extends A{}") + << QStringLiteral(u"export default class extends A{}"); + + // export default Expression + QTest::newRow("Default_Expression") << QStringLiteral(u"export default 1+1") + << QStringLiteral(u"export default 1 + 1;"); + QTest::newRow("Default_ArrowFunctionExpression") + << QStringLiteral(u"export default(x,y)=> x+2") + << QStringLiteral(u"export default (x, y) => x + 2;"); + } + + // https://262.ecma-international.org/7.0/#prod-ExportDeclaration + void exportDeclarations() + { + QFETCH(QString, exportToBeFormatted); + QFETCH(QString, expectedFormattedExport); + + QString formattedExport = formatJSModuleCode(exportToBeFormatted); + + QEXPECT_FAIL("ExportClause_Specifier_as_StringLiteral", + "export {a as \"string name\"} declaration is not supported yet", Abort); + QEXPECT_FAIL("star_as_Specifier", "export * as star declaration is not supported yet", + Abort); + QEXPECT_FAIL("Default_AnonymousClass", "QTBUG-122291", Abort); + QEXPECT_FAIL("Default_AnonymousFunction", "QTBUG-122291", Abort); + QEXPECT_FAIL("Default_AnonymousGenerator", "QTBUG-122291", Abort); + QCOMPARE(formattedExport, expectedFormattedExport); + } + + void carryoverMJS_data() + { + QTest::addColumn<QString>("codeToBeFormatted"); + QTest::addColumn<int>("maxLineLength"); + QTest::addColumn<QString>("expectedFormattedCode"); + + QTest::newRow("LongExportList_NoMaxLineLength") + << QStringLiteral(u"export const n1=1,n2=2,n3=3,n4=4,n5=5") << -1 + << QStringLiteral(u"export const n1 = 1, n2 = 2, n3 = 3, n4 = 4, n5 = 5;"); + QTest::newRow("LongExportList_MaxLineLength20") + << QStringLiteral(u"export const n1=1,n2=2,n3=3,n4=4,n5=5") << 20 + << QStringLiteral(u"export const n1 = 1,\n" + u" n2 = 2, n3 = 3,\n" + u" n4 = 4, n5 = 5;"); + } + + void carryoverMJS() + { + QFETCH(QString, codeToBeFormatted); + QFETCH(int, maxLineLength); + QFETCH(QString, expectedFormattedCode); + + LineWriterOptions lwOptions; + lwOptions.maxLineLength = maxLineLength; + // TODO maybe fetch this + lwOptions.formatOptions.indentSize = 2; + QString formattedCode = formatJSModuleCode(codeToBeFormatted, lwOptions); + + QEXPECT_FAIL("LongExportList_MaxLineLength20", "QTBUG-122260", Abort); + QCOMPARE(formattedCode, expectedFormattedCode); + } + + void importDeclarations_data() + { + QTest::addColumn<QString>("importToBeFormatted"); + QTest::addColumn<QString>("expectedFormattedImport"); + // not exhaustive list of ExportDeclarations as per + // https://262.ecma-international.org/7.0/#prod-ImportDeclaration + + // import ModuleSpecifier; + QTest::newRow("ModuleSpecifier") + << QStringLiteral(u"import \"Module\"") << QStringLiteral(u"import \"Module\";"); + + // import ImportClause FromClause ; + QTest::newRow("NameSpaceImport") << QStringLiteral(u"import * as d from \"design\";") + << QStringLiteral(u"import * as d from \"design\";"); + + QTest::newRow("NamedImports") << QStringLiteral(u"import {b,cd as c,d} from \"M\";") + << QStringLiteral(u"import { b, cd as c, d } from \"M\";"); + + QTest::newRow("DefaultBindung") << QStringLiteral(u"import defaultExport from \"M\"") + << QStringLiteral(u"import defaultExport from \"M\";"); + QTest::newRow("DefaultBindung_NameSpaceImport") + << QStringLiteral(u"import defaultExport, * as m from \"M\";") + << QStringLiteral(u"import defaultExport, * as m from \"M\";"); + QTest::newRow("DefaultBinding_NamedImports") + << QStringLiteral(u"import defaultExport,{b,cd as c,d} from \"M\";") + << QStringLiteral(u"import defaultExport, { b, cd as c, d } from \"M\";"); + + QTest::newRow("ImportClause_Specifier_as_StringLiteral") + << QStringLiteral(u"import{\"s\" as s} from \"M\"") + << QStringLiteral(u"import { \"s\" as s } from \"M\";"); + } + + // https://262.ecma-international.org/7.0/#prod-ImportDeclaration + void importDeclarations() + { + QFETCH(QString, importToBeFormatted); + QFETCH(QString, expectedFormattedImport); + + QString formattedImport = formatJSModuleCode(importToBeFormatted); + + QEXPECT_FAIL( + "ImportClause_Specifier_as_StringLiteral", + "import {\"string literal export\" as alias } declaration is not supported yet", + Abort); + QCOMPARE(formattedImport, expectedFormattedImport); + } + + void methodDefinitions_data() + { + QTest::addColumn<QString>("methodToBeFormatted"); + QTest::addColumn<QString>("expectedFormattedMethod"); + + // ObjectInitializer + QTest::newRow("ObjGetter") << QStringLiteral(u"const o={get a(){},}") + << QStringLiteral(u"const o = {\nget a(){}\n}"); + QTest::newRow("ObjSetter") << QStringLiteral(u"const o={set a(a){},}") + << QStringLiteral(u"const o = {\nset a(a){}\n}"); + QTest::newRow("ComputedObjPropertyGetter") + << QStringLiteral(u"const o={get [a+b](){},}") + << QStringLiteral(u"const o = {\nget [a + b](){}\n}"); + + // Generator + QTest::newRow("ObjPropertyGenerator") + << QStringLiteral(u"const o={*a(){1+1;},}") + << QStringLiteral(u"const o = {\n*a(){\n1 + 1;\n}\n}"); + QTest::newRow("ComputedClassPropertyGenerator") + << QStringLiteral(u"class A{*[a+b](){}}") + << QStringLiteral(u"class A {\n*[a + b](){}\n}"); + + // ClassDefinitions + QTest::newRow("ClassGetter") << QStringLiteral(u"class A{get a(){}}") + << QStringLiteral(u"class A {\nget a(){}\n}"); + QTest::newRow("ClassSetter") << QStringLiteral(u"class A{set a(a){}}") + << QStringLiteral(u"class A {\nset a(a){}\n}"); + } + + // https://262.ecma-international.org/7.0/#sec-method-definitions + void methodDefinitions() + { + QFETCH(QString, methodToBeFormatted); + QFETCH(QString, expectedFormattedMethod); + + QString formattedMethod = formatJSCode(methodToBeFormatted); + + QCOMPARE(formattedMethod, expectedFormattedMethod); + } + + void statementList_data() + { + QTest::addColumn<QString>("codeToBeFormatted"); + QTest::addColumn<QString>("expectedFormattedCode"); + + QTest::newRow("StatementsOnTheSameLine") + << QStringLiteral(u"a=1;b=1;") << QStringLiteral(u"a = 1;\nb = 1;"); + + QTest::newRow("StatementsOnSuccessiveLines") + << QStringLiteral(u"a=1;\nb=1;") << QStringLiteral(u"a = 1;\nb = 1;"); + + QTest::newRow("EmptyLineBetweenStatements") + << QStringLiteral(u"a=1;\n\nb=1;") << QStringLiteral(u"a = 1;\n\nb = 1;"); + + QTest::newRow("MultipleEmptyLinesBetweenStatements") + << QStringLiteral(u"a=1;\n\n\n\n\n\nb=1;") << QStringLiteral(u"a = 1;\n\nb = 1;"); + + QTest::newRow("MultilineStatementWithStatementOnTheFollowingLine") + << QStringLiteral(u"console.log(\n\n);\nb = 1;") + << QStringLiteral(u"console.log();\nb = 1;"); + + QTest::newRow("StatementWithPostCommentAndStatementOnTheFollowingLine") + << QStringLiteral(u"a=1;//\nb=1;") << QStringLiteral(u"a = 1;//\nb = 1;"); + + QTest::newRow("StatementWithPostCommentAndEmptyLineToNextStatement") + << QStringLiteral(u"a=1;//\n\nb=1;") << QStringLiteral(u"a = 1;//\n\nb = 1;"); + + QTest::newRow("StatementWithPostCommentAndMultipleEmptyLinesToNextStatement") + << QStringLiteral(u"a=1;//\n\n\n\n\nb=1;") << QStringLiteral(u"a = 1;//\n\nb = 1;"); + + QTest::newRow("StatementsWithCommentInBetweenThem") + << QStringLiteral(u"a=1;\n//\nb=1;") << QStringLiteral(u"a = 1;\n//\nb = 1;"); + + QTest::newRow("StatementsWithCommentAndSingleEmptyLineInBetweenThem") + << QStringLiteral(u"a=1;\n\n//\n\nb=1;") + << QStringLiteral(u"a = 1;\n\n//\n\nb = 1;"); + + QTest::newRow("StatementsWithCommentAndMultipleEmptyLinesInBetweenThem") + << QStringLiteral(u"a=1;\n\n\n\n//\n\n\nb=1;") + << QStringLiteral(u"a = 1;\n\n//\n\nb = 1;"); + + QTest::newRow("StatementWithSingleEmptyLineAndPreCommentOnNextStatement") + << QStringLiteral(u"a=1;\n\n//\nb=1;") << QStringLiteral(u"a = 1;\n\n//\nb = 1;"); + + QTest::newRow("StatementWithMultipleEmptyLinesAndPreCommentOnNextStatement") + << QStringLiteral(u"a=1;\n\n\n\n\n\n\n\n//\nb=1;") + << QStringLiteral(u"a = 1;\n\n//\nb = 1;"); + } + + void statementList() + { + QFETCH(QString, codeToBeFormatted); + QFETCH(QString, expectedFormattedCode); + + QString formattedCode = formatJSCode(codeToBeFormatted); + + QCOMPARE(formattedCode, expectedFormattedCode); + } + private: }; diff --git a/tests/auto/qmldom/standalone/CMakeLists.txt b/tests/auto/qmldom/standalone/CMakeLists.txt deleted file mode 100644 index e141ec0835..0000000000 --- a/tests/auto/qmldom/standalone/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -##################################################################### -## tst_dom_all Binary executing all tests together -## (simpler to verify coverage) -##################################################################### -# Collect test data -file(GLOB_RECURSE test_data_glob - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. - domdata/*) -list(APPEND test_data ${test_data_glob}) - -set(QMLDOM_EXTERNAL_BUILD OFF CACHE BOOL "If the build is against an external Qt, and not tested inside a build of this Qt" FORCE) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/qmldom/standalone qmldomlib) - -if(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") -elseif (MINGW) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj") -endif() - -qt_internal_add_test(tst_standalone - SOURCES - tst_standalone.cpp - DEFINES - QT_DEPRECATED_WARNINGS - QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/../domdata" - LIBRARIES - Qt::Core - Qt::QmlPrivate - qmldomlib - TESTDATA ${test_data} -) - -qt_internal_extend_target(tst_standalone CONDITION ANDROID OR IOS - DEFINES - QT_QMLTEST_DATADIR=":/domdata" -) diff --git a/tests/auto/qmldom/standalone/tst_standalone.cpp b/tests/auto/qmldom/standalone/tst_standalone.cpp deleted file mode 100644 index 14b6424525..0000000000 --- a/tests/auto/qmldom/standalone/tst_standalone.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tst_standalone.h" -QTEST_MAIN(QQmlJS::Dom::TestStandalone) diff --git a/tests/auto/qmldom/standalone/tst_standalone.h b/tests/auto/qmldom/standalone/tst_standalone.h deleted file mode 100644 index c403c660e6..0000000000 --- a/tests/auto/qmldom/standalone/tst_standalone.h +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include <QtTest/QtTest> -#ifdef QMLDOM_STANDALONE -# include "qmldom/qqmldom_global.h" -// common declarations -# include "qmldom/qqmldomitem_p.h" -// comparisons of two DomItems -# include "qmldom/qqmldomcompare_p.h" -// field filters to compare only selected fields (ignore for example location changes) -# include "qmldom/qqmldomfieldfilter_p.h" -// needed to edit and cast to concrete type (PropertyDefinition, ScriptExpression,...) -# include "qmldom/qqmldomelements_p.h" -// cast of the top level items (DomEnvironments,...) -# include "qmldom/qqmldomtop_p.h" -#else -# include <QtQmlDom/qqmldom_global.h> -// common declarations -# include <QtQmlDom/private/qqmldomitem_p.h> -// comparisons of two DomItems -# include <QtQmlDom/private/qqmldomcompare_p.h> -// field filters to compare only selected fields (ignore for example location changes) -# include <QtQmlDom/private/qqmldomfieldfilter_p.h> -// needed to edit and cast to concrete type (PropertyDefinition, ScriptExpression,...) -# include <QtQmlDom/private/qqmldomelements_p.h> -// cast of the top level items (DomEnvironments,...) -# include <QtQmlDom/private/qqmldomtop_p.h> -#endif - -#include <QDebug> -#include <QLatin1String> -#include <QLatin1Char> -#include <QLibraryInfo> -#include <QDir> - -QT_BEGIN_NAMESPACE -namespace QQmlJS { -namespace Dom { - -class TestStandalone : public QObject -{ - Q_OBJECT -private slots: - void testLoadEditSave() - { - QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter"); - QStringList qmltypeDirs = - QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); - - // qDebug() << "Creating an environment loading qml from the directories" << qmltypeDirs; - // qDebug() << "single threaded, no dependencies"; - DomItem env = DomEnvironment::create( - qmltypeDirs, - QQmlJS::Dom::DomEnvironment::Option::SingleThreaded - | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); - - QString testFilePath = baseDir + QLatin1String("/file1.qml"); - DomItem tFile; // place where to store the loaded file - - // qDebug() << "loading the file" << testFilePath; - env.loadFile( - testFilePath, QString(), - [&tFile](Path, const DomItem &, const DomItem &newIt) { - tFile = newIt; // callback called when everything is loaded that receives the - // loaded external file pair (path, oldValue, newValue) - }, - LoadOption::DefaultLoad); - - // trigger the load - env.loadPendingDependencies(); - - // # Read only API: DomItem is a generic pointer for read only access to Dom Itmes :) - { - // ## declarative json like API - DomItem qmlFile = tFile.field(Fields::currentItem); - DomItem imports = qmlFile.field(Fields::imports); - DomItem qmlObj = qmlFile.field(Fields::components) - .key(QString()) - .index(0) - .field(Fields::objects) - .index(0); - - // ### Dump - // any DomItem can be dumped - // qDebug() << "writing to QDebug dumps that element:" << imports; - // often the dump is too verbose, and one might want it to a separate file - QString dumpFilePath = QDir(QDir::tempPath()) - .filePath(QFileInfo(testFilePath).baseName() - + QLatin1String(".dump.json")); - qmlFile.dump(dumpFilePath, FieldFilter::defaultFilter()); - // qDebug() << "dumped file to" << dumpFilePath; - - // ### Paths - // qDebug() << "To identify a DomItem a canonical path can be used:" - // << imports.canonicalPath(); - // a path can be converted to/from strings - QString pString = imports.canonicalPath().toString(); - Path importsPath = Path::fromString(pString); - // and loaded again using the .path(somePath) method - DomItem imports2 = env.path(importsPath); - QCOMPARE(imports, imports2); - // the canonical path is absolute, but you can have relative paths - Path first = Path::Index(0); - DomItem firstImport = imports.path(first); - // an existing path can also be extended - Path firstImportPath = importsPath.index(0); - QCOMPARE(firstImportPath, firstImport.canonicalPath()); - // the normal elements of a path are index, key, field - // Uppercase static method creates one, lowercase appends to an existing path. - Path mainComponentPath = Path::Field(Fields::components).key("").index(0); - DomItem mainComponent = qmlFile.path(mainComponentPath); - // DomItems have the same methods to access their elements - DomItem mainComponent2 = qmlFile.field(Fields::components).key("").index(0); - // two other special ements are root (root element for absolute paths) - Path topPath = Path::Root(PathRoot::Top); - QCOMPARE(topPath, importsPath[0]); - // the current element performs an operation (tipically a lookup or iteration) at the - // current path location (not handled here) - Path lookupPath = Path::Current(PathCurrent::Lookup); - - // there are various visit methods to iterate/visit DomItems in particular visitTree - // which is quite flexible. - // They normally use callbacks that can return false to stop the iteration. - // Still often the DomKind specific for loop presentated later are clearer and more - // convenient - { - QString s; - QTextStream dbg(&s); - imports.visitTree( - Path(), - [&dbg](Path p, const DomItem &el, bool) { - dbg << QStringLiteral(u" ").repeated(p.length()) << "*" - << p.last().toString() << " " << domKindToString(el.domKind()) - << "(" << el.internalKindStr() << ")\n"; - // returning false here stops the whole iteration - return true; - }, - VisitOption::Default, // we want a recursive visit visiting also the top and - // adopted - [&dbg](Path p, const DomItem &, bool canonicalChild) { - // returning false here skips that branch - if (!canonicalChild) { - dbg << QStringLiteral(u" ").repeated(p.length()) << "+" - << p.last().toString() << " (adopted, will not recurse)\n"; - } else if (p && p.headIndex(0) % 2 == 1) { - dbg << QStringLiteral(u" ").repeated(p.length()) << "-" - << p.last().toString() << " *recursive visit skipped*\n"; - return false; // we skip odd entries in lists; - } else { - dbg << QStringLiteral(u" ").repeated(p.length()) << "+" - << p.last().toString() << "\n"; - } - return true; - }, - [&dbg](Path p, const DomItem &, bool) { - dbg << QStringLiteral(u" ").repeated(p.length()) << "=" - << p.last().toString() << "\n"; - return true; - }); - dbg.flush(); - QCOMPARE(s, - QStringLiteral("* List(List)\n" - "+\n" - " *[0] Object(Import)\n" - " +[0]\n" - " *.uri Value(ConstantData)\n" - " +.uri\n" - " =.uri\n" - " *.version Object(Version)\n" - " +.version\n" - " *.majorVersion Value(ConstantData)\n" - " +.majorVersion\n" - " =.majorVersion\n" - " *.minorVersion Value(ConstantData)\n" - " +.minorVersion\n" - " =.minorVersion\n" - " *.isLatest Value(ConstantData)\n" - " +.isLatest\n" - " =.isLatest\n" - " *.isValid Value(ConstantData)\n" - " +.isValid\n" - " =.isValid\n" - " *.stringValue Value(ConstantData)\n" - " +.stringValue\n" - " =.stringValue\n" - " =.version\n" - " *.implicit Value(ConstantData)\n" - " +.implicit\n" - " =.implicit\n" - " *.comments Object(RegionComments)\n" - " +.comments\n" - " =.comments\n" - " =[0]\n" - " *[1] Object(Import)\n" - " -[1] *recursive visit skipped*\n" - " *[2] Object(Import)\n" - " +[2]\n" - " *.uri Value(ConstantData)\n" - " +.uri\n" - " =.uri\n" - " *.version Object(Version)\n" - " +.version\n" - " *.majorVersion Value(ConstantData)\n" - " +.majorVersion\n" - " =.majorVersion\n" - " *.minorVersion Value(ConstantData)\n" - " +.minorVersion\n" - " =.minorVersion\n" - " *.isLatest Value(ConstantData)\n" - " +.isLatest\n" - " =.isLatest\n" - " *.isValid Value(ConstantData)\n" - " +.isValid\n" - " =.isValid\n" - " *.stringValue Value(ConstantData)\n" - " +.stringValue\n" - " =.stringValue\n" - " =.version\n" - " *.implicit Value(ConstantData)\n" - " +.implicit\n" - " =.implicit\n" - " *.comments Object(RegionComments)\n" - " +.comments\n" - " =.comments\n" - " =[2]\n" - " *[3] Object(Import)\n" - " -[3] *recursive visit skipped*\n" - " *[4] Object(Import)\n" - " +[4]\n" - " *.uri Value(ConstantData)\n" - " +.uri\n" - " =.uri\n" - " *.version Object(Version)\n" - " +.version\n" - " *.majorVersion Value(ConstantData)\n" - " +.majorVersion\n" - " =.majorVersion\n" - " *.minorVersion Value(ConstantData)\n" - " +.minorVersion\n" - " =.minorVersion\n" - " *.isLatest Value(ConstantData)\n" - " +.isLatest\n" - " =.isLatest\n" - " *.isValid Value(ConstantData)\n" - " +.isValid\n" - " =.isValid\n" - " *.stringValue Value(ConstantData)\n" - " +.stringValue\n" - " =.stringValue\n" - " =.version\n" - " *.comments Object(RegionComments)\n" - " +.comments\n" - " =.comments\n" - " =[4]\n" - "=\n")); - } - - // ### DomKind - // any DomItem belongs to 5 basic types - - // 1. Object (a C++ object) - QCOMPARE(qmlFile.domKind(), DomKind::Object); - // The underlying type of the c++ object can be found with .internalKind() - QCOMPARE(qmlFile.internalKind(), DomType::QmlFile); - // .initernalKindStr() is a convenience string version of it - QCOMPARE(qmlFile.internalKindStr(), u"QmlFile"); - // the object attributes (fields) can be reached using .field(u"filedName") - // normally one should not use a string, but the Fields:: constant - DomItem qmlFile2 = tFile.field(Fields::currentItem); - // all the available fields can be listed via fields() - // qDebug() << "The" << qmlObj.internalKindStr() << "at" << qmlObj.canonicalPath() - // << "has the following fields:" << qmlObj.fields(); - // we can access the underlying C++ object with as<> - // if (const QmlFile *qmlFilePtr = qmlFile.as<QmlFile>()) - // qDebug() << "The QmlFile lives at the address" << qmlFilePtr; - //// We can get the shared pointer of the owner type (which for the file is the QmlFile - /// itself - // if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) - // qDebug() << "QmlFile uses shared pointers as ownership method, the underlying - // address " - // "is the same" - // << qmlFilePtr.get(); - - // 2. a (Cbor-) Value, i.e a string, number,... - DomItem fPath = qmlFile.field(Fields::canonicalFilePath); - QCOMPARE(fPath.domKind(), DomKind::Value); - // the Cbor representation of a value can be extracted with .value(), and in this case - // we can then call toString - // qDebug() << "The filePath DomItem is " << fPath << " and it still 'knows' its path " - // << fPath.canonicalPath() << " but can have it also as value:" << - // fPath.value() - // << "or even better as string." << - // fPath.value().toString(QLatin1String("*none*")); - // a DomItem might have a valid value() even if it is not of type DomKind::Value, indeed - // CBor maps and lists are mapped to DomKind::Map and DomKind::List, and can be - // traversed thought that but also have a valid value(). - - // 3. a list - QCOMPARE(imports.domKind(), DomKind::List); - // the number of elements can be sound with .indexes() and with .index(n) we access each - // element - // qDebug() << "We have " << imports.indexes() << " imports, and the first is " - // << imports.index(0); - // If we want to just loop on the elements .values() is the most convenient way - // technically values *always* works even for objects and maps, iterating on the values - // for (DomItem import : imports.values()) { - // if (const Import *importPtr = import.as<Import>()) { - // if (importPtr->implicit) - // qDebug() << importPtr->uri << importPtr->version.stringValue(); - // } - //} - - // 4. a map - DomItem bindings = qmlObj.field(Fields::bindings); - QCOMPARE(bindings.domKind(), DomKind::Map); - // The keys of the map can be reached either with .keys() or .sortedKeys(), each element - // with .key(k) - // qDebug() << "bindings"; - // for (QString k : bindings.sortedKeys()) { - // for (DomItem b : bindings.key(k).values()) { - // qDebug() << k << ":" << b; - // } - //} - - // 5 The empty element - DomItem empty; - QCOMPARE(empty.domKind(), DomKind::Empty); - // The empty element is the only DomItem that casted to bool returns false, so checking - // for it can be just an implicit cast to bool - QVERIFY(bindings && !empty); - // the empty element supports all the previus operations so that one can traverse a non - // existing path without checking at every element, but only check the result - DomItem nonExisting = qmlFile.field(u"no-existing").key(u"a").index(0); - QVERIFY(!nonExisting); - - // the index operator [] can be used instead of .index/.key/.field, it might be slightly - // less efficient but works - - // find type - // access type - - // ### write out - // it is possible to write out a qmlFile (actually also parts of it), which will - // automatically reformat it - QString reformattedFilePath = - QDir(QDir::tempPath()) - .filePath(QFileInfo(testFilePath).baseName() + QLatin1String(".qml")); - DomItem newFile = qmlFile.writeOut(reformattedFilePath); - // qDebug() << "reformatted written at " << reformattedFilePath; - - // ## Jumping around - // ### Generic Methods - // from a DomItem you do no have just deeper in the tree, you can also go up the - // hierarch toward the root .container() just goes up one step in the canonicalPath of - // the object - QCOMPARE(imports, firstImport.container()); - // .containingObject() goes up to the containing DomKind::Object, skipping over all Maps - // and Lists - QCOMPARE(qmlFile, firstImport.containingObject()); - // .owner() returns the shared pointer based "owner" object, qmlFile and - // ScriptExpression are owningItems - QCOMPARE(qmlFile, bindings.owner()); - // .top() goes to the top of the tree, i.e the environment (or the universe) - QCOMPARE(env, bindings.top()); - // environment is normally the same as top, but making sure it is a actually a - // DomEnvironment - QCOMPARE(env, bindings.environment()); - // the universe is a cache of loaded files which for each file keeps two versions: the - // latest and the latest valid it can be reached with .universe(), from the universe you - // cannot get back to the environment. - QCOMPARE(env.universe().internalKind(), DomType::DomUniverse); - - // ## QML Oriented Methods - // The Dom model is not for generic json-like structures, so there are methods tailored - // for Qml and its structure The methods can succeed if there is a clearly defined - // unique result. sometime there is an obivious, but not necessarily unique choice - // (tipically going up the hierarchy), for example given a qml file the obvious choice - // for a component is the root component, but the file might contain other inline - // components, and for an object with different version exposed (C++ property - // versioning) the latest version is the natural choice, but other might be available. - // In these case passing GoTo::MostLikely as argument makes the method to this obivious - // choice (or possibly even only choice if no other versions/components are actually - // defined), instead of refusing any potentially ambiguous situation and returning the - // empty element. - - // .fileObject() goes to the object representing the whole file - // (from either the external object returned by load or from inside the file) - DomItem fileObject = tFile.fileObject(); - DomItem fileObject2 = imports.fileObject(); - QCOMPARE(fileObject, fileObject2); - QCOMPARE(fileObject.internalKind(), DomType::QmlFile); - // .component() goes to the component object. - QCOMPARE(qmlObj.component(), qmlFile.component(GoTo::MostLikely)); - // .pragmas gives access to the pragmas of the current component - QCOMPARE(qmlFile.pragmas(), qmlFile.field(Fields::pragmas)); - - // QmlObject - // QmlObject if the main to represent the type information (methods, bindings, - // properties,...) of qml. Please note that QmlObject -> component operation is - // potentially lossy, when multiple version are exposed, so we represent a type through - // its root object, not through a component. - - // .qmlObject() goes to the current QmlObject - QCOMPARE(qmlObj, bindings.qmlObject()); - - // Given the centrality of QmlObject several of its attributes have convenience methods - // to access them: - - // .children() makes subObjects contained inside a QmlObject accessible - // note that it is possible to add objects also by directly binding the children or data - // attribute, those children are not listed here, this accesses only those listed inside - // the QmlObject - QCOMPARE(qmlObj.children(), qmlObj.field(Fields::children)); - DomItem subObj0 = qmlObj.children().index(0); - // .child(<i>) is a shortcut for .children.index(<i>) - QCOMPARE(subObj0, qmlObj.child(0)); - // rootQmlObject goes to the root qmlObject (unless one reaches an empty element) - QVERIFY(bool(subObj0)); - QCOMPARE(subObj0.rootQmlObject(), qmlObj); - // .bindings() returns the bindings defined in the current object - QCOMPARE(bindings, qmlObj.bindings()); - DomItem mCompObj = qmlObj.child(0) - .child(0) - .bindings() - .key(u"delegate") - .index(0) - .field(Fields::value) - .child(1); - // .methods() gives methods definitions and signals - DomItem methods = mCompObj.methods(); - // qDebug() << "mCompObj methods:"; - for (QString methodName : methods.sortedKeys()) { - for (DomItem method : methods.key(methodName).values()) { - if (const MethodInfo *methodPtr = method.as<MethodInfo>()) { - QCOMPARE(methodName, methodPtr->name); - // qDebug() << " " << methodPtr->name << methodPtr->methodType; - } - } - } - // qDebug() << "mCompObj propertyDefs:"; - // .propertyDefs() returns the properties defined in the current object - DomItem pDefs = mCompObj.propertyDefs(); - for (QString pDefName : pDefs.sortedKeys()) { - for (DomItem pDef : pDefs.key(pDefName).values()) { - if (const PropertyDefinition *pDefPtr = pDef.as<PropertyDefinition>()) { - QCOMPARE(pDefName, pDefPtr->name); - // qDebug() << " " << pDefPtr->name << pDefPtr->typeName; - } - } - } - // binding and property definitions are about the ones defined in the current object - // often one is interested also to the inherited properties. - // Here PropertyInfo helps, it list all the definitions and bindings for a given - // property in the inheritance order (local definitions, parent definitions, parent - // parent definitions,...) .propertyInfos() gives access in the usual way (through a - // DomItem) - DomItem propertyInfos = mCompObj.propertyInfos(); - // .propertyInfoWithName(<name>) directly accesses one - PropertyInfo pInfo = mCompObj.propertyInfoWithName(QStringLiteral(u"a")); - // qDebug() << "bindings" << pInfo.bindings; - // .propertyInfoNames() gives the names of the properties - QCOMPARE(propertyInfos.keys(), mCompObj.propertyInfoNames()); - - // .globalScope() goes to the globa scope object - QCOMPARE(qmlObj.globalScope().internalKind(), DomType::GlobalScope); - // and scope to the containing scope - QCOMPARE(bindings.scope(), qmlObj); - } - // mutate & edit - { - // DomItem handles read-only access, but if one wants to change something it cannot be - // used. MutableDomItem can be initialized with a DomItem, and provides also the methods - // to modify the item. It keeps the OwningItem and the path to the current item. - // Mutability can invalidate pointers to non owning items (and thus DomItem). - // For this reason one should not modify something that other code can have a DomItem - // pointer to, the best practice is to make shared object immutable and never change - // them. One should modify only a copy that is used only by a single thread, and do not - // shared untils all modifications are done. A MutableItem stays valid (or becomes - // Empty), but stays safe to use - // - // Assuming one guarantees that editing is ok, doing it in practice is just about using - // MutableDomItem instead of DomItem - // It is possible to simply initialize a mutable item with a DomItem - DomItem origFile = tFile.fileObject(); - MutableDomItem myFile0(origFile); - // Normally it is better to have a separate environment. Is possible to avoid re-reading - // the files already read by sharing the Universe between two environments. - // But normally it is better and just as safe to work on a copy, so that one can be sure - // that no DomItem is kept by other code gets invalidated. The .makeCopy creates a deep - // copy, and by default (DomItem::CopyOption::EnvConnected) creates an environment which - // to takes all non local elements from the current environment (its parent environment) - // but replaces the file object with the copy. When finished one can replace the file - // object of the parent with the new one using .commitToBase(). - MutableDomItem myFile = origFile.makeCopy(); - QVERIFY(myFile.ownerAs<QmlFile>() - && myFile.ownerAs<QmlFile>() != myFile0.ownerAs<QmlFile>()); - QVERIFY(myFile.environment().ownerAs<DomEnvironment>() - && myFile.environment().ownerAs<DomEnvironment>() - != myFile0.environment().ownerAs<DomEnvironment>()); - // we can check that the two files are really identical (.item() give back the DomItem - // of a MutableDomItem - Q_ASSERT(domCompareStrList(origFile, myFile, FieldFilter::compareFilter()).isEmpty()); - // MutableDomItem has the same methods as DomItem - MutableDomItem qmlObj = myFile.qmlObject(GoTo::MostLikely); - MutableDomItem qmlObj2 = myFile.field(Fields::components) - .key(QString()) - .index(0) - .field(Fields::objects) - .index(0); - QVERIFY(bool(qmlObj)); - QCOMPARE(qmlObj, qmlObj2); - // qDebug() << "mutable qmlObj has canonicalPath " << qmlObj.canonicalPath(); - // but it adds methods to add - // * new PropertyDefinitions - PropertyDefinition b; - b.name = QLatin1String("xx"); - b.typeName = QLatin1String("int"); - // if we make t true we also have to give a value... - MutableDomItem addedPDef = qmlObj.addPropertyDef(b); - // qDebug() << "added property definition at:" << addedPDef.pathFromOwner(); - // * new bindings - MutableDomItem addedBinding0 = qmlObj.addBinding( - Binding("height", - std::shared_ptr<ScriptExpression>(new ScriptExpression( - QStringLiteral(u"243"), - ScriptExpression::ExpressionType::BindingExpression)))); - // by default addBinding, addPropertyDef and addMethod have the AddOption::Override - // to make it more difficult to create invalid documents, so that only the - // following binding remains (where we use the convenience constructor that constucts - // the ScriptExpression internally - MutableDomItem addedBinding = - qmlObj.addBinding(Binding("height", QStringLiteral(u"242"))); - // qDebug() << "added binding at:" << addedBinding.pathFromOwner(); - // * new methods - MethodInfo mInfo; - mInfo.name = QLatin1String("foo2"); - MethodParameter param; - param.name = QLatin1String("x"); - mInfo.parameters.append(param); - mInfo.setCode(QLatin1String("return 4*10+2 - x")); - // we can change the added binding - addedBinding.setCode(QLatin1String("245")); - MutableDomItem addedMethod = qmlObj.addMethod(mInfo); - // qDebug() << "added method at:" << addedMethod.pathFromOwner(); - // * new QmlObjects - QmlObject subObj; - subObj.setName(QLatin1String("Item")); - MutableDomItem addedSubObj = qmlObj.addChild(subObj); - // qDebug() << "added subObject at:" << addedMethod.pathFromOwner(); - // It is possible to modify the content of objects, using the mutableAs method - if (PropertyDefinition *addedPDefPtr = addedPDef.mutableAs<PropertyDefinition>()) { - addedPDefPtr->isRequired = true; - } - MutableDomItem firstChild = qmlObj.child(0); - // qDebug() << "firstChild:" << firstChild; - // It is possible remove objects - if (QmlObject *qmlObjPtr = qmlObj.mutableAs<QmlObject>()) { - QList<QmlObject> children = qmlObjPtr->children(); - children.removeAt(0); - qmlObjPtr->setChildren(children); - } - // But as MutableDomItem does not keep the identity, just the same position, the - // addedSubObj becomes invalid (and firstChild changes) - // qDebug() << "after removal firstChild:" << firstChild; - // qDebug() << "addedSubObj becomes invalid:" << addedSubObj; - // qDebug() << "But the last object is the added one:" - // << qmlObj.child(qmlObj.children().indexes() - 1); - - // now origFile are different - Q_ASSERT(!domCompareStrList(origFile, myFile, FieldFilter::compareFilter()).isEmpty()); - // and we can look at the places where they differ - // qDebug().noquote().nospace() - // << "Edits introduced the following diffs (ignoring file locations" - // << " and thus whitespace/reformatting changes):\n" - // << domCompareStrList(origFile, myFile, FieldFilter::noLocationFilter(), - // DomCompareStrList::AllDiffs) - // .join(QString()); - - QString reformattedFilePath = - QDir(QDir::tempPath()) - .filePath(QStringLiteral(u"edited") + QFileInfo(testFilePath).baseName() - + QLatin1String(".qml")); - Q_ASSERT(myFile.as<QmlFile>() - && myFile.as<QmlFile>() == myFile.fileObject().as<QmlFile>()); - MutableDomItem reformattedEditedFile = myFile.writeOut(reformattedFilePath); - // the reformatted edited file might be different from the edited file - // but the differences are just in file location/formatting - Q_ASSERT(domCompareStrList(myFile, reformattedEditedFile, - FieldFilter::noLocationFilter()) - .isEmpty()); - - // qDebug() << "The edited file was written at " << reformattedFilePath; - QString dumpFilePath = QDir(QDir::tempPath()) - .filePath(QStringLiteral(u"edited0") - + QFileInfo(testFilePath).baseName() - + QLatin1String(".dump.json")); - myFile.dump(dumpFilePath); - // qDebug() << "The non reformatted edited file was dumped at " << dumpFilePath; - QString reformattedDumpFilePath = - QDir(QDir::tempPath()) - .filePath(QStringLiteral(u"edited") + QFileInfo(testFilePath).baseName() - + QLatin1String(".dump.json")); - reformattedEditedFile.dump(reformattedDumpFilePath); - // qDebug() << "The edited file was dumped at " << reformattedDumpFilePath; - // The top environment still contains the original loaded file - Q_ASSERT(origFile.ownerAs<QmlFile>() != reformattedEditedFile.ownerAs<QmlFile>()); - Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>()); - QCOMPARE(tFile.fileObject().refreshed().ownerAs<QmlFile>(), - origFile.ownerAs<QmlFile>()); - QCOMPARE(tFile.fileObject().ownerAs<QmlFile>(), origFile.ownerAs<QmlFile>()); - Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>() - != reformattedEditedFile.ownerAs<QmlFile>()); - // we can commit the reformatted file - if (!reformattedEditedFile.commitToBase()) { - qWarning() << "No reformatted file to commit"; - } - // myFile might not be the same (If and updated check is requested, not the case here) - if (myFile.ownerAs<QmlFile>() != reformattedEditedFile.ownerAs<QmlFile>() - && !myFile.commitToBase()) { - qWarning() << "Could not commit edited file"; - } - // but refreshing it (looking up its canonical path) we always find the updated file - QCOMPARE(myFile.refreshed().ownerAs<QmlFile>(), - reformattedEditedFile.ownerAs<QmlFile>()); - Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>() - == reformattedEditedFile.ownerAs<QmlFile>()); - } - } -}; - -} // Dom -} // QQmlJS -QT_END_NAMESPACE diff --git a/tests/auto/qmldom/stringdumper/CMakeLists.txt b/tests/auto/qmldom/stringdumper/CMakeLists.txt index a67041d433..2b09281302 100644 --- a/tests/auto/qmldom/stringdumper/CMakeLists.txt +++ b/tests/auto/qmldom/stringdumper/CMakeLists.txt @@ -7,6 +7,12 @@ ## tst_qmldomstringdumper Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmldomstringdumper LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + qt_internal_add_test(tst_qmldomstringdumper SOURCES tst_qmldomstringdumper.cpp tst_qmldomstringdumper.h diff --git a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp index bc25070838..fab3bc5be8 100644 --- a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp +++ b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "tst_qmldomstringdumper.h" QTEST_MAIN(QQmlJS::Dom::TestStringDumper) diff --git a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h index 9d72b589c1..28e844db73 100644 --- a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h +++ b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h @@ -1,5 +1,5 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TST_QMLDOMSTRINGDUMPER_H #define TST_QMLDOMSTRINGDUMPER_H |