diff options
Diffstat (limited to 'tests/auto/qmldom')
107 files changed, 6449 insertions, 602 deletions
diff --git a/tests/auto/qmldom/CMakeLists.txt b/tests/auto/qmldom/CMakeLists.txt index 00fb7850f4..f3186e2263 100644 --- a/tests/auto/qmldom/CMakeLists.txt +++ b/tests/auto/qmldom/CMakeLists.txt @@ -1,6 +1,12 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmldom.pro. add_subdirectory(errormessage) add_subdirectory(domitem) add_subdirectory(path) add_subdirectory(stringdumper) +add_subdirectory(merging) +add_subdirectory(reformatter) +add_subdirectory(combined) diff --git a/tests/auto/qmldom/combined/CMakeLists.txt b/tests/auto/qmldom/combined/CMakeLists.txt new file mode 100644 index 0000000000..add44acf0b --- /dev/null +++ b/tests/auto/qmldom/combined/CMakeLists.txt @@ -0,0 +1,46 @@ +# 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) +##################################################################### + +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}/.. + domdata/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_dom_all + SOURCES + tst_dom_all.cpp + ../stringdumper/tst_qmldomstringdumper.h + ../errormessage/tst_qmldomerrormessage.cpp ../errormessage/tst_qmldomerrormessage.h + ../path/tst_qmldompath.h + ../domitem/tst_qmldomitem.h + ../merging/tst_dommerging.h + ../reformatter/tst_reformatter.h + INCLUDE_DIRECTORIES + .. + DEFINES + NO_QTEST_MAIN + QT_DEPRECATED_WARNINGS + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/../domdata" + LIBRARIES + Qt::Core + Qt::QmlDomPrivate + Qt::Test + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_dom_all CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/domdata" +) diff --git a/tests/auto/qmldom/combined/tst_dom_all.cpp b/tests/auto/qmldom/combined/tst_dom_all.cpp new file mode 100644 index 0000000000..6e3e0bd851 --- /dev/null +++ b/tests/auto/qmldom/combined/tst_dom_all.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "stringdumper/tst_qmldomstringdumper.h" +#include "errormessage/tst_qmldomerrormessage.h" +#include "domitem/tst_qmldomitem.h" +#include "merging/tst_dommerging.h" +#include "path/tst_qmldompath.h" +#include "reformatter/tst_reformatter.h" + +#include <QtCore/qdebug.h> + +int main(int argc, char *argv[]) +{ + int status = 0; + { + QQmlJS::Dom::TestStringDumper test; + status |= QTest::qExec(&test, argc, argv); + } + { + QQmlJS::Dom::PathEls::TestPaths test; + status |= QTest::qExec(&test, argc, argv); + } + { + QQmlJS::Dom::TestErrorMessage test; + status |= QTest::qExec(&test, argc, argv); + } + { + QQmlJS::Dom::TestDomItem test; + status |= QTest::qExec(&test, argc, argv); + } + { + QQmlJS::Dom::TestDomMerging test; + status |= QTest::qExec(&test, argc, argv); + } + { + QQmlJS::Dom::TestReformatter 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/MyComponent.qml b/tests/auto/qmldom/domdata/domitem/MyComponent.qml new file mode 100644 index 0000000000..e661503f12 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/MyComponent.qml @@ -0,0 +1,5 @@ +import QtQuick + +Rectangle { + text: "bla" +} diff --git a/tests/auto/qmldom/domdata/domitem/MySingleton.qml b/tests/auto/qmldom/domdata/domitem/MySingleton.qml new file mode 100644 index 0000000000..241b38b664 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/MySingleton.qml @@ -0,0 +1,7 @@ +pragma Singleton +import QtQuick 2.12 + +QtObject { + signal mySignal() + function myFunc() {} +} diff --git a/tests/auto/qmldom/domdata/domitem/TestImports.qml b/tests/auto/qmldom/domdata/domitem/TestImports.qml new file mode 100644 index 0000000000..8581cd2611 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/TestImports.qml @@ -0,0 +1,10 @@ +import QtQuick +import "../.." +import "../dommerging" +import "C:/some/path" +import "http://bla.com/" +import "/absolute/path" + +Item { + +} 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 new file mode 100644 index 0000000000..e2521b7afe --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/aliasProperties.qml @@ -0,0 +1,53 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +import QtQuick + +Item { + id: root + property alias objRef1: root + property alias objRef2: obj1 + property int p1: 1 + property alias a1i: root.p1 + property alias a2q: obj1.objectName + property Item i1: Item{ + id: it1 + objectName: "it1" + property QtObject i11: QtObject { + id: it11 + objectName: "it11" + property int p1: 42 + } + } + QtObject { + id: obj1 + objectName:"obj1" + property alias objRef2: obj2 + property real p1: 2.0 + property int p2: 33 + property alias a3i: root.p1 + property alias a5i: obj1.a4i + property alias a4i: root.a1i + property alias a6r: obj2.p2r + } + QtObject { + id: obj2 + objectName:"obj2" + property real p2r: 3.0 + property alias a8i: obj1.a3i + property alias a9q: root.a2q + property alias a11I: it1.objectName + property alias a11Q: it1.i11.objectName + property alias a12q: obj1.objectName + } + Rectangle { + color: "red" + Text{ + id:t1 + text: obj2.a11Q + } + Text{ + text: obj1.objRef2.p2r + anchors.top: t1.bottom + } + } +} 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/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/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 new file mode 100644 index 0000000000..7998642ef5 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/invalidAliasProperties.qml @@ -0,0 +1,46 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +import QtQuick + +Item { + id: root + property alias loop1: root.loop1 + property alias loop2: obj1.loop3 + property int p1: 1 + property Item i1: Item{ + id: it1 + property Item i11: Item{ + id: it11 + property int p1: 42 + } + } + QtObject { + id: obj1 + property alias objRef2: obj2 + property real p1: 2.0 + property alias loop3: root.loop2 + property alias loop4: root.loop1 + property alias a3i: root.p1 + property alias a7i: obj2.a8i // loop error + } + QtObject { + id: obj2 + property alias a8i: obj1.a3i + property alias tooDeep: root.i1.i11.p1 + property alias invalid1: noId + property alias invalid2: noId.objectName + property alias a13i: obj1.objRef2.a8i // invalid property a8i error + } + property alias tooDeepRef: obj2.tooDeep + Rectangle { + color: "red" + Text{ + id:t1 + text: obj1.p1 + } + Text{ + text: obj2.a8i + anchors.top: t1.bottom + } + } +} 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/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/test1.qml b/tests/auto/qmldom/domdata/domitem/test1.qml new file mode 100644 index 0000000000..4a28129ee9 --- /dev/null +++ b/tests/auto/qmldom/domdata/domitem/test1.qml @@ -0,0 +1,32 @@ +import QtQuick +import QtQuick as QQ + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Scroll") + + Rectangle { + anchors.fill: parent + + ListView { + width: parent.width + model: { + MySingleton.mySignal() + 20 + } + delegate: ItemDelegate { + id: root + text: "Item " + (index + 1) + width: parent.width + Rectangle { + text: "bla" + } + MyComponent { + text: root.text + } + } + } + } +} 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/dommerging/test1.qml b/tests/auto/qmldom/domdata/dommerging/test1.qml new file mode 100644 index 0000000000..6347510c3a --- /dev/null +++ b/tests/auto/qmldom/domdata/dommerging/test1.qml @@ -0,0 +1,12 @@ +import QtQuick + +Rectangle { +id: root + +width: { height *3/4 } +height: 480 +Rectangle { + width: 58 + heigth:80 +} +} diff --git a/tests/auto/qmldom/domdata/reformatter/MyComponent.qml b/tests/auto/qmldom/domdata/reformatter/MyComponent.qml new file mode 100644 index 0000000000..e661503f12 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/MyComponent.qml @@ -0,0 +1,5 @@ +import QtQuick + +Rectangle { + text: "bla" +} diff --git a/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml b/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml new file mode 100644 index 0000000000..5058d08ae3 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml @@ -0,0 +1,6 @@ +Item { + arrow1: (x) => x + arrow2: (x) => x + x + arrow3: (x) => x > 3 ? x * x : x + arrow4: (x) => { return x > 3 ? x * x : x; } +} diff --git a/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml b/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml new file mode 100644 index 0000000000..4b0bbabd3d --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml @@ -0,0 +1,8 @@ +Item { + arrow1: x => x + arrow2: x => x + x + arrow3: x => x > 3 ? x * x : x + arrow4: x => { + return x > 3 ? x * x : x; + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/commentedFile.qml b/tests/auto/qmldom/domdata/reformatter/commentedFile.qml new file mode 100644 index 0000000000..1f64a0913b --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/commentedFile.qml @@ -0,0 +1,38 @@ +// pre comment +import QtQuick 2.15 + +// pre item comment +/* multi +line */ // comment after multi line +Item { +function method(x) // function comment +{ // just returns the double of x + return 2*x // yep twice as much +} // post method +// binding comment +a: {// header + +// before x() +// before x() +x() // after x +// before y = 8 + z + zz + +// before y = 8 + z + zz +y = 8 + +// before z +z + // after z +// 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 +} +// post binding comment +} +// footer file comment +/* second comment */ /* third comment */ diff --git a/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml new file mode 100644 index 0000000000..ab722f94be --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted.qml @@ -0,0 +1,40 @@ +// pre comment +import QtQuick 2.15 + +// pre item comment +/* multi +line */ // comment after multi line +Item { + function method(x) // function comment + { // just returns the double of x + return 2 * x; // yep twice as much + } // post method + + // binding comment + a: { + // header + + // before x() + // before x() + x(); // after x + // before y = 8 + z + zz + + // before y = 8 + z + zz + y = 8 + + // before z + z + // after z + // 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 + } + // post binding comment +} +// footer file comment +/* second comment */ /* third comment */ diff --git a/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml new file mode 100644 index 0000000000..c6e33f9389 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/commentedFileReformatted2.qml @@ -0,0 +1,40 @@ +// pre comment +import QtQuick 2.15 + +// pre item comment +/* multi +line */ // comment after multi line +Item { + function method(x) // function comment + { // just returns the double of x + return 2 * x; // yep twice as much + } // post method + + // binding comment + a: { + // header + + // before x() + // before x() + x(); // after x + // before y = 8 + z + zz + + // before y = 8 + z + zz + y = 8 + + // before z + z + // after z + // 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 + } + // post binding comment +} +// footer file comment +/* second comment */ /* third comment */ diff --git a/tests/auto/qmldom/domdata/reformatter/file1.qml b/tests/auto/qmldom/domdata/reformatter/file1.qml new file mode 100644 index 0000000000..e0382bf57c --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/file1.qml @@ -0,0 +1,47 @@ +pragma pippo +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Scroll") + + property var arr: [1,2,3] + property var arrTrailingComma: [1,2,3,] + + Rectangle { + anchors.fill: parent + + Behavior on opacity {} + + ListView { + width: parent.width + model: { + MySingleton.mySignal() + 20 + } + delegate: ItemDelegate { + id: root + text: "Item " + (index + 1) + width: parent.width + Rectangle { + text: "bla" + } + MyComponent { + text: root.text + function f(v = 4){ + let c = 0 + return { + a: function(){ if (b == 0) c += 78*5*v; }() + } + } + property int a: { + return 45 + } + } + } + } + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml b/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml new file mode 100644 index 0000000000..6a24f907d1 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/file1Reformatted.qml @@ -0,0 +1,56 @@ +pragma pippo +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + property var arr: [1, 2, 3] + property var arrTrailingComma: [1, 2, 3,] + + height: 480 + title: qsTr("Scroll") + visible: true + width: 640 + + Rectangle { + anchors.fill: parent + + Behavior on opacity { + } + + ListView { + model: { + MySingleton.mySignal(); + 20; + } + width: parent.width + + delegate: ItemDelegate { + id: root + + text: "Item " + (index + 1) + width: parent.width + + Rectangle { + text: "bla" + } + MyComponent { + property int a: { + return 45; + } + + function f(v = 4) { + let c = 0; + return { + a: function () { + if (b == 0) + c += 78 * 5 * v; + }() + }; + } + + text: root.text + } + } + } + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml b/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml new file mode 100644 index 0000000000..2e7483f453 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/file1Reformatted2.qml @@ -0,0 +1,50 @@ +pragma pippo +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Scroll") + + property var arr: [1, 2, 3] + property var arrTrailingComma: [1, 2, 3,] + + Rectangle { + anchors.fill: parent + + Behavior on opacity {} + + ListView { + width: parent.width + model: { + MySingleton.mySignal(); + 20; + } + delegate: ItemDelegate { + id: root + text: "Item " + (index + 1) + width: parent.width + Rectangle { + text: "bla" + } + MyComponent { + text: root.text + function f(v = 4) { + let c = 0; + return { + a: function () { + if (b == 0) + c += 78 * 5 * v; + }() + }; + } + property int a: { + return 45; + } + } + } + } + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml b/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml new file mode 100644 index 0000000000..4085b91e6e --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/file1Unindented.qml @@ -0,0 +1,47 @@ +pragma pippo +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { +visible: true +width: 640 +height: 480 +title: qsTr("Scroll") + +property var arr: [1,2,3] +property var arrTrailingComma: [1,2,3,] + +Rectangle { +anchors.fill: parent + +Behavior on opacity {} + +ListView { +width: parent.width +model: { +MySingleton.mySignal() +20 +} +delegate: ItemDelegate { +id: root +text: "Item " + (index + 1) +width: parent.width +Rectangle { +text: "bla" +} +MyComponent { +text: root.text +function f(v = 4){ +let c = 0 +return { +a: function(){ if (b == 0) c += 78*5*v; }() +} +} +property int a: { +return 45 +} +} +} +} +} +} diff --git a/tests/auto/qmldom/domdata/reformatter/file2.qml b/tests/auto/qmldom/domdata/reformatter/file2.qml new file mode 100644 index 0000000000..5aeebc14e6 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/file2.qml @@ -0,0 +1,58 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Scroll") + + onTrigger: console.log("trigger signal emitted") + + onSend: { + console.log("send signal emitted with notice: " + notice) + } + + Rectangle { + anchors.fill: parent + + ListView { + width: parent.width + model: { + MySingleton.mySignal() + 20 + } + delegate: ItemDelegate { + id: root + text: "Item " + (index + 1) + width: parent.width + Rectangle { + text: "bla" + } + MyComponent { + text: root.text + function f(v){ + let c = 0 + return { + a: function(){ if (b == 0) c += 78*5*v; }() + } + } + property int a: { + let x = isNaN; + (45) + x ? 5 + 1 : 8 + } + property list<Item> b: [ Item{ + width: 5 + }, + Item{ + width: 6 + }] + } + } + } + } + Component.onCompleted: { + console.log("loaded") + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml b/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml new file mode 100644 index 0000000000..3ed3aa208c --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/file2Reformatted.qml @@ -0,0 +1,67 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + height: 480 + title: qsTr("Scroll") + visible: true + width: 640 + + Component.onCompleted: { + console.log("loaded"); + } + onSend: { + console.log("send signal emitted with notice: " + notice); + } + onTrigger: console.log("trigger signal emitted") + + Rectangle { + anchors.fill: parent + + ListView { + model: { + MySingleton.mySignal(); + 20; + } + width: parent.width + + delegate: ItemDelegate { + id: root + + text: "Item " + (index + 1) + width: parent.width + + Rectangle { + text: "bla" + } + MyComponent { + property int a: { + let x = isNaN; + (45); + x ? 5 + 1 : 8; + } + property list<Item> b: [ + Item { + width: 5 + }, + Item { + width: 6 + } + ] + + function f(v) { + let c = 0; + return { + a: function () { + if (b == 0) + c += 78 * 5 * v; + }() + }; + } + + text: root.text + } + } + } + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/inline.qml b/tests/auto/qmldom/domdata/reformatter/inline.qml new file mode 100644 index 0000000000..3429e9a2af --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/inline.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { +component CustomText: Text { +color: "red" +text: "Test Text" +} +} diff --git a/tests/auto/qmldom/domdata/reformatter/inlineReformatted.qml b/tests/auto/qmldom/domdata/reformatter/inlineReformatted.qml new file mode 100644 index 0000000000..749da25332 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/inlineReformatted.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { + component CustomText: Text { + color: "red" + text: "Test Text" + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/noMerge.qml b/tests/auto/qmldom/domdata/reformatter/noMerge.qml new file mode 100644 index 0000000000..e8606f5d10 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/noMerge.qml @@ -0,0 +1,14 @@ +import QtQuick + +Item{ + property var arrayNoMerge + property var array2: [1,2,3] + property list<Item> array: [Item {}, Item{ nr: 2 }] + default property list<Item> nonMergeable + property list<Item> singleObjList + singleObjList: Item{ + el: "alone" + } + nonMergeable: [Item {}, Item{ nr: "II" }] + arrayNoMerge: [Item {}, Item{ nr: 2 }] +} diff --git a/tests/auto/qmldom/domdata/reformatter/noMergeReformatted.qml b/tests/auto/qmldom/domdata/reformatter/noMergeReformatted.qml new file mode 100644 index 0000000000..a441885bb7 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/noMergeReformatted.qml @@ -0,0 +1,33 @@ +import QtQuick + +Item { + property list<Item> array: [ + Item { + }, + Item { + nr: 2 + } + ] + property var array2: [1, 2, 3] + property var arrayNoMerge + default property list<Item> nonMergeable + property list<Item> singleObjList + + arrayNoMerge: [ + Item { + }, + Item { + nr: 2 + } + ] + nonMergeable: [ + Item { + }, + Item { + nr: "II" + } + ] + singleObjList: Item { + el: "alone" + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/required.qml b/tests/auto/qmldom/domdata/reformatter/required.qml new file mode 100644 index 0000000000..29103114bd --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/required.qml @@ -0,0 +1,13 @@ +import QtQuick 2.15 + +Item { + id: theFoo + + required property Item theItem + required data + + function foo() { + theItem.foo("The issue is exacerbated if the object literal is wrapped onto the next line like so:", + {"foo": theFoo}) + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/requiredReformatted.qml b/tests/auto/qmldom/domdata/reformatter/requiredReformatted.qml new file mode 100644 index 0000000000..2f9420a22e --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/requiredReformatted.qml @@ -0,0 +1,14 @@ +import QtQuick 2.15 + +Item { + id: theFoo + + required data + required property Item theItem + + function foo() { + theItem.foo("The issue is exacerbated if the object literal is wrapped onto the next line like so:", { + "foo": theFoo + }); + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml b/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml new file mode 100644 index 0000000000..7473283605 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/requiredReformatted2.qml @@ -0,0 +1,14 @@ +import QtQuick 2.15 + +Item { + id: theFoo + + required data + required property Item theItem + + function foo() { + theItem.foo("The issue is exacerbated if the object literal is wrapped onto the next line like so:", { + "foo": theFoo + }); + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/spread.qml b/tests/auto/qmldom/domdata/reformatter/spread.qml new file mode 100644 index 0000000000..e8c30d0643 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/spread.qml @@ -0,0 +1,15 @@ +import QtQuick 2.15 + +Item { + function myFunction() {} + function foo() { + iterableObj = [1,2] + obj = {a:42} + let x = (console.log("bla\n"), 3); + myFunction(...iterableObj); // pass all elements of iterableObj as arguments to function myFunction + let arr = [...iterableObj, '4', 'five', 6]; // combine two arrays by inserting all elements from iterableObj + //let objClone = { ...obj }; // pass all key:value pairs from an object (ES2018) + myFunction(-1, ...args, 2, ...[3]); + console.log(Math.max(...[1, 2, 3, 4])) + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml b/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml new file mode 100644 index 0000000000..fde8ffd686 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/spreadReformatted.qml @@ -0,0 +1,18 @@ +import QtQuick 2.15 + +Item { + function foo() { + iterableObj = [1, 2]; + obj = { + a: 42 + }; + let x = (console.log("bla\n"), 3); + myFunction(...iterableObj); // pass all elements of iterableObj as arguments to function myFunction + let arr = [...iterableObj, '4', 'five', 6]; // combine two arrays by inserting all elements from iterableObj + //let objClone = { ...obj }; // pass all key:value pairs from an object (ES2018) + myFunction(-1, ...args, 2, ...[3]); + console.log(Math.max(...[1, 2, 3, 4])); + } + function myFunction() { + } +} diff --git a/tests/auto/qmldom/domdata/reformatter/template.qml b/tests/auto/qmldom/domdata/reformatter/template.qml new file mode 100644 index 0000000000..886922838b --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/template.qml @@ -0,0 +1,22 @@ +import QtQuick 2.15 + +Text { + property int i: 3 + text: "´"+ + "m + l + + "+ + 'b'+' + multi + l + l'+ + `template` + + `t${i+6}`+ + `t${i+`nested${i}`}`+ + `t${function(){return 5}()}`+ + `t\${i} + ${i + + 2}` + width: 300 +} diff --git a/tests/auto/qmldom/domdata/reformatter/templateReformatted.qml b/tests/auto/qmldom/domdata/reformatter/templateReformatted.qml new file mode 100644 index 0000000000..bfc8d28db1 --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/templateReformatted.qml @@ -0,0 +1,17 @@ +import QtQuick 2.15 + +Text { + property int i: 3 + + text: "´" + "m + l + + " + 'b' + ' + multi + l + l' + `template` + `t${i + 6}` + `t${i + `nested${i}`}` + `t${function () { + return 5; + }()}` + `t\${i} + ${i + 2}` + width: 300 +} diff --git a/tests/auto/qmldom/domdata/reformatter/typeAnnotations.qml b/tests/auto/qmldom/domdata/reformatter/typeAnnotations.qml new file mode 100644 index 0000000000..63c4f5471c --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/typeAnnotations.qml @@ -0,0 +1,12 @@ +import QtQuick + +Item { +function calculateWidth(object: Item<a>): list<Item> { +var w = object.width / 3 +// ... +// more javascript code +// ... +console.debug(w) +return w +} +} diff --git a/tests/auto/qmldom/domdata/reformatter/typeAnnotationsReformatted.qml b/tests/auto/qmldom/domdata/reformatter/typeAnnotationsReformatted.qml new file mode 100644 index 0000000000..3b304176fb --- /dev/null +++ b/tests/auto/qmldom/domdata/reformatter/typeAnnotationsReformatted.qml @@ -0,0 +1,12 @@ +import QtQuick + +Item { + function calculateWidth(object: Item<a>): list<Item> { + var w = object.width / 3; + // ... + // more javascript code + // ... + console.debug(w); + return w; + } +} diff --git a/tests/auto/qmldom/domitem/CMakeLists.txt b/tests/auto/qmldom/domitem/CMakeLists.txt index 71983f0cdd..e6f8117902 100644 --- a/tests/auto/qmldom/domitem/CMakeLists.txt +++ b/tests/auto/qmldom/domitem/CMakeLists.txt @@ -1,15 +1,37 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from domitem.pro. ##################################################################### ## 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}/.. + domdata/domitem/*) +list(APPEND test_data ${test_data_glob}) + qt_internal_add_test(tst_qmldomitem SOURCES - tst_qmldomitem.cpp + tst_qmldomitem.cpp tst_qmldomitem.h DEFINES QT_DEPRECATED_WARNINGS - PUBLIC_LIBRARIES - Qt::QmlDevToolsPrivate + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/../domdata" + LIBRARIES Qt::QmlDomPrivate + Qt::Test + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qmldomitem CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/domdata" ) diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.cpp b/tests/auto/qmldom/domitem/tst_qmldomitem.cpp index bec8db8c4a..97b7dcc3ed 100644 --- a/tests/auto/qmldom/domitem/tst_qmldomitem.cpp +++ b/tests/auto/qmldom/domitem/tst_qmldomitem.cpp @@ -1,190 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -**/ -#include <QtQmlDom/private/qqmldomitem_p.h> -#include <QtQmlDom/private/qqmldomtop_p.h> - -#include <QtTest/QtTest> -#include <QCborValue> -#include <QDebug> - -#include <memory> - -QT_BEGIN_NAMESPACE -namespace QQmlJS { -namespace Dom { - -DomItem wrapInt(const DomItem &self, Path p, const int &i){ - return self.subDataPath(p, i).item; -} - -class TestDomItem: public QObject -{ - Q_OBJECT -public: - -private slots: - void initTestCase() { - universePtr = std::make_shared<DomUniverse>(QStringLiteral(u"dummyUniverse")); - envPtr = std::make_shared<DomEnvironment>(universePtr, QStringList()); - env = DomItem(envPtr); - } - - void testList() { - QList<int> l({1,2,3,4}); - QList<int> l2 = l; - QList<int> l3({1}); - QList<int> l4 = l3; - QCOMPARE(&(l[1]), &(l[1])); - QCOMPARE(&(l3[0]), &(l3[0])); - // QCOMPARE(&(l3[0]), &(l4[0])); // shallow copy actually copies els (QVector behavior)... - DomItem list1 = env.subList( - List::fromQListRef<int>(Path::field(u"list"), l, &wrapInt)).item; - DomItem list2 = env.subList( - List::fromQListRef<int>(Path::field(u"reverseList"), l, &wrapInt, ListOptions::Reverse)).item; - QCOMPARE(list1.domKind(), DomKind::List); - QCOMPARE(list1.indexes(), 4); - QCOMPARE(list1[0].value().toInteger(), 1); - QCOMPARE(list1[3].value().toInteger(), 4); - QVERIFY(!list1[4]); - QCOMPARE(list1[4].value().toInteger(-1), -1); - QVERIFY(list1[0].value() != list2[0].value()); - QCOMPARE(list1[0].value(), list2[3].value()); - QCOMPARE(list1[3].value(), list2[0].value()); - QCOMPARE(list1.container(), env); - } - void testMap() { - QMap<QString, int> map({{QStringLiteral(u"a"),1},{QStringLiteral(u"b"),2}}); - //QMap<QString, int> map2 = map; - QMap<QString, int> map3({{QStringLiteral(u"a"),1}}); - //QMap<QString, int> map4 = map3; - auto it = map.find(QStringLiteral(u"a")); - auto it2 = map.find(QStringLiteral(u"a")); - auto it3 = map3.find(QStringLiteral(u"a")); - auto it4 = map3.find(QStringLiteral(u"a")); - //auto it5 = map4.find(QStringLiteral(u"a")); - QVERIFY(it != map.end()); - QVERIFY(it2 != map.end()); - QCOMPARE(&(*it), &(*it2)); - QCOMPARE(&(*it), &(map[QStringLiteral(u"a")])); - QCOMPARE(&(it.value()), &(it2.value())); - //QCOMPARE(&(*it), &(map2[QStringLiteral(u"a")])); - QCOMPARE(&(*it3), &(*it4)); - //QCOMPARE(&(*it3), &(*it5)); - DomItem map1 = env.subMap( - Map::fromMapRef<int>( - Path::field(u"map"), map, - &wrapInt)).item; - QCOMPARE(map1.domKind(), DomKind::Map); - QCOMPARE(map1[u"a"].value().toInteger(), 1); - QCOMPARE(map1.key(QStringLiteral(u"a")).value().toInteger(), 1); - QCOMPARE(map1[u"b"].value().toInteger(), 2); - QVERIFY(!map1[u"c"]); - QCOMPARE(map1.container(), env); - } - void testMultiMap() { - QMultiMap<QString, int> mmap({{QStringLiteral(u"a"),1},{QStringLiteral(u"b"),2},{QStringLiteral(u"a"),3}}); - //QMultiMap<QString, int> mmap2 = mmap; - QMultiMap<QString, int> mmap3({{QStringLiteral(u"a"),1}}); - //QMultiMap<QString, int> mmap4 = mmap3; - auto it = mmap.find(QStringLiteral(u"a")); - auto it2 = mmap.find(QStringLiteral(u"a")); - //auto it3 = mmap2.find(QStringLiteral(u"a")); - auto it4 = mmap3.find(QStringLiteral(u"a")); - auto it5 = mmap3.find(QStringLiteral(u"a")); - //auto it6 = mmap4.find(QStringLiteral(u"a")); - QVERIFY(it != mmap.end()); - QVERIFY(it2 != mmap.end()); - QCOMPARE(&(it.value()), &(it2.value())); - QCOMPARE(&(*it), &(it2.value())); - //QCOMPARE(&(*it), &(*it2)); // copy has different address (copies elements for int) - //QCOMPARE(&(*it), &(*it3)); - QCOMPARE(&(*it4), &(*it5)); - //QCOMPARE(&(*it4), &(*it6)); - DomItem map1 = env.subMap( - Map::fromMultiMapRef<int>( - Path::field(u"mmap"), mmap, - &wrapInt)).item; - QCOMPARE(map1[u"b"].index(0).value().toInteger(), 2); - QVERIFY(!map1[u"b"].index(2)); - QVERIFY(!map1[u"c"]); - QCOMPARE(map1[u"a"][0].value().toInteger(), 1); - QCOMPARE(map1.key(QStringLiteral(u"a")).index(0).value().toInteger(), 1); - QCOMPARE(map1.key(QStringLiteral(u"a")).index(1).value().toInteger(), 3); - QCOMPARE(map1.container(), env); - } - void testReference() { - Path p = Path::root(u"env"); - DomItem ref = env.subReferenceField(u"ref",p).item; - QCOMPARE(ref.field(u"referredObjectPath").value().toString(), p.toString()); - QCOMPARE(ref.fields(), QList<QString>({QStringLiteral(u"referredObjectPath"), QStringLiteral(u"get")})); - QCOMPARE(ref.field(u"get").internalKind(), DomType::DomEnvironment); - } - void testEnvUniverse() { - QCOMPARE(env.internalKind(), DomType::DomEnvironment); - QCOMPARE(env.pathFromOwner(), Path()); - QCOMPARE(env.containingObject().internalKind(), DomType::Empty); - QCOMPARE(env.container().internalKind(), DomType::Empty); - QCOMPARE(env.canonicalPath(), Path::root(u"env")); - QCOMPARE(env.path(u"$env").internalKind(), DomType::DomEnvironment); - QCOMPARE(env.top().internalKind(), DomType::DomEnvironment); - QCOMPARE(env.environment().internalKind(), DomType::DomEnvironment); - QCOMPARE(env.owningItemPtr(), envPtr); - QCOMPARE(env.topPtr(), envPtr); - DomItem univ = env.universe(); - QCOMPARE(univ.internalKind(), DomType::DomUniverse); - QCOMPARE(univ.owningItemPtr(), universePtr); - DomItem univ2 = env.path(u".universe"); - QCOMPARE(univ2.internalKind(), DomType::DomUniverse); - QCOMPARE(univ2.owningItemPtr(), universePtr); - QCOMPARE(univ2.topPtr(), universePtr); - DomItem univ3 = env.field(u"universe"); - QCOMPARE(univ3.internalKind(), DomType::DomUniverse); - } -private: - std::shared_ptr<DomUniverse> universePtr; - std::shared_ptr<DomEnvironment> envPtr; - DomItem env; -}; - - -} // namespace Dom -} // namespace QQmlJS -QT_END_NAMESPACE +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "tst_qmldomitem.h" QTEST_MAIN(QQmlJS::Dom::TestDomItem) -#include "tst_qmldomitem.moc" diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.h b/tests/auto/qmldom/domitem/tst_qmldomitem.h new file mode 100644 index 0000000000..4464333b6e --- /dev/null +++ b/tests/auto/qmldom/domitem/tst_qmldomitem.h @@ -0,0 +1,3421 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QMLDOMITEM_H +#define TST_QMLDOMITEM_H +#include <QtQmlDom/private/qqmldomitem_p.h> +#include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQmlDom/private/qqmldomastdumper_p.h> +#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> +#include <QtCore/QDebug> +#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(const DomItem &self, const PathEls::PathComponent &p, const int &i) +{ + return self.subDataItem(p, i); +} + +class DomTestClass +{ +public: + std::shared_ptr<int> i; +}; + +class TestDomItem : public QObject +{ + Q_OBJECT +public: + static ErrorGroups myErrors() + { + static ErrorGroups res { { NewErrorGroup("tests"), NewErrorGroup("domitem") } }; + return res; + } + +private slots: + void initTestCase() + { + baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/domitem"); + 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, + DomCreationOption::None, universePtr)); + env = DomItem(envPtr); + testOwnerPtr = std::shared_ptr<MockOwner>(new MockOwner( + Path::Root(u"env").field(u"testOwner"), 0, + QMap<QString, MockObject> { + MockObject( + Path::Field(u"obj1"), + QMap<QString, MockObject> { + MockObject( + Path::Field(u"obj1").field(u"obj1_2"), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(3) }, + { QLatin1String("val2"), QCborValue(4) } }) + .asStringPair() }, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(1) }, + { QLatin1String("val2"), QCborValue(2) } }) + .asStringPair(), + MockObject( + Path::Field(u"obj2"), + QMap<QString, MockObject> { + MockObject( + Path::Field(u"obj2").field(u"obj2_2"), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(5) }, + { QLatin1String("val2"), QCborValue(6) }, + { QLatin1String("valX"), + QCborValue(QStringLiteral(u"pippo")) } }) + .asStringPair() }, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(7) }, + { QLatin1String("val2"), QCborValue(8) } }) + .asStringPair() }, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(9) }, + }, + QMap<QString, QMap<QString, MockObject>> { + { QStringLiteral(u"map"), + QMap<QString, MockObject> { + MockObject(Path::Field(u"map").key(u"a"), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(10) }, + { QLatin1String("val2"), QCborValue(11) } }) + .asStringPair(), + MockObject(Path::Field(u"map").key(u"b"), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(12) }, + { QLatin1String("val2"), QCborValue(13) } }) + .asStringPair() } } }, + QMap<QString, QMultiMap<QString, MockObject>> { + { QStringLiteral(u"mmap"), + QMultiMap<QString, MockObject> { + { QStringLiteral(u"a"), + MockObject( + Path::Field(u"mmap").key(u"a").index(0), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(14) }, + { QLatin1String("val2"), QCborValue(15) } }) }, + { QStringLiteral(u"a"), + MockObject(Path::Field(u"mmap").key(u"a").index(1), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(16) }, + { QLatin1String("val2"), + QCborValue(17) } }) } } } }, + QMap<QString, QList<MockObject>> { + { QStringLiteral(u"list"), + QList<MockObject> { + MockObject(Path::Field(u"list").index(0), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(18) }, + { QLatin1String("val2"), QCborValue(19) } }), + MockObject(Path::Field(u"list").index(1), + QMap<QString, MockObject> {}, + QMap<QString, QCborValue> { + { QStringLiteral(u"val1"), QCborValue(20) }, + { QLatin1String("val2"), + QCborValue(21) } }) } } })); + envPtr->setExtraOwningItem(QStringLiteral(u"testOwner"), testOwnerPtr); + tOwner = env.field(u"testOwner"); + } + + void testList() + { + QList<int> l({ 1, 2, 3, 4 }); + QList<int> l2 = l; + QList<int> l3({ 1 }); + QList<int> l4 = l3; + QCOMPARE(&(l[1]), &(l[1])); + QCOMPARE(&(l3[0]), &(l3[0])); + // QCOMPARE(&(l3[0]), &(l4[0])); // shallow copy actually copies els (QVector behavior)... + DomItem list1 = env.subListItem(List::fromQListRef<int>(Path::Field(u"list"), l, &wrapInt)); + DomItem list2 = env.subListItem(List::fromQListRef<int>(Path::Field(u"reverseList"), l, + &wrapInt, ListOptions::Reverse)); + QCOMPARE(list1.domKind(), DomKind::List); + QCOMPARE(list1.indexes(), 4); + QCOMPARE(list1[0].value().toInteger(), 1); + QCOMPARE(list1[3].value().toInteger(), 4); + QVERIFY(!list1[4]); + QCOMPARE(list1[4].value().toInteger(-1), -1); + QVERIFY(list1[0].value() != list2[0].value()); + QCOMPARE(list1[0].value(), list2[3].value()); + QCOMPARE(list1[3].value(), list2[0].value()); + } + void testMap() + { + QMap<QString, int> map({ { QStringLiteral(u"a"), 1 }, { QStringLiteral(u"b"), 2 } }); + // QMap<QString, int> map2 = map; + QMap<QString, int> map3({ { QStringLiteral(u"a"), 1 } }); + // QMap<QString, int> map4 = map3; + auto it = map.find(QStringLiteral(u"a")); + auto it2 = map.find(QStringLiteral(u"a")); + auto it3 = map3.find(QStringLiteral(u"a")); + auto it4 = map3.find(QStringLiteral(u"a")); + // auto it5 = map4.find(QStringLiteral(u"a")); + QVERIFY(it != map.end()); + QVERIFY(it2 != map.end()); + QCOMPARE(&(*it), &(*it2)); + QCOMPARE(&(*it), &(map[QStringLiteral(u"a")])); + QCOMPARE(&(it.value()), &(it2.value())); + // QCOMPARE(&(*it), &(map2[QStringLiteral(u"a")])); + QCOMPARE(&(*it3), &(*it4)); + // QCOMPARE(&(*it3), &(*it5)); + DomItem map1 = env.subMapItem(Map::fromMapRef<int>(Path::Field(u"map"), map, &wrapInt)); + QCOMPARE(map1.domKind(), DomKind::Map); + QCOMPARE(map1[u"a"].value().toInteger(), 1); + QCOMPARE(map1.key(QStringLiteral(u"a")).value().toInteger(), 1); + QCOMPARE(map1[u"b"].value().toInteger(), 2); + QVERIFY(!map1[u"c"]); + } + void testMultiMap() + { + QMultiMap<QString, int> mmap({ { QStringLiteral(u"a"), 1 }, + { QStringLiteral(u"b"), 2 }, + { QStringLiteral(u"a"), 3 } }); + // QMultiMap<QString, int> mmap2 = mmap; + QMultiMap<QString, int> mmap3({ { QStringLiteral(u"a"), 1 } }); + // QMultiMap<QString, int> mmap4 = mmap3; + auto it = mmap.find(QStringLiteral(u"a")); + auto it2 = mmap.find(QStringLiteral(u"a")); + // auto it3 = mmap2.find(QStringLiteral(u"a")); + auto it4 = mmap3.find(QStringLiteral(u"a")); + auto it5 = mmap3.find(QStringLiteral(u"a")); + // auto it6 = mmap4.find(QStringLiteral(u"a")); + QVERIFY(it != mmap.end()); + QVERIFY(it2 != mmap.end()); + QCOMPARE(&(it.value()), &(it2.value())); + QCOMPARE(&(*it), &(it2.value())); + // QCOMPARE(&(*it), &(*it2)); // copy has different address (copies elements for int) + // QCOMPARE(&(*it), &(*it3)); + QCOMPARE(&(*it4), &(*it5)); + // QCOMPARE(&(*it4), &(*it6)); + DomItem map1 = env.subMapItem(Map::fromMultiMapRef<int>(Path::Field(u"mmap"), mmap)); + QCOMPARE(map1[u"b"].index(0).value().toInteger(), 2); + QVERIFY(!map1[u"b"].index(2)); + QVERIFY(!map1[u"c"]); + QCOMPARE(map1[u"a"][0].value().toInteger(), 1); + QCOMPARE(map1.key(QStringLiteral(u"a")).index(0).value().toInteger(), 1); + QCOMPARE(map1.key(QStringLiteral(u"a")).index(1).value().toInteger(), 3); + { + QMultiMap<QString, DomTestClass> m1; + m1.insert(QStringLiteral(u"xx"), DomTestClass { std::shared_ptr<int>(new int(4)) }); + QCOMPARE(m1.begin().value().i.use_count(), 1); + QMultiMap<QString, DomTestClass> m2 = m1; + m1.clear(); + auto it = m2.cbegin(); + auto end = m2.cend(); + while (it != end) { + QCOMPARE(it.value().i.use_count(), 1); + m1.insert(it.key(), it.value()); + QCOMPARE(it.value().i.use_count(), 2); + ++it; + } + m2.insert(QStringLiteral(u"xy"), DomTestClass { std::shared_ptr<int>(new int(8)) }); + QMultiMap<QString, DomTestClass> m3 = m2; + m3.begin().value() = DomTestClass { std::shared_ptr<int>(new int(2)) }; + auto it2 = m2.begin(); + auto it3 = m3.begin(); + QCOMPARE(*it2.value().i, 4); + QCOMPARE(*it3.value().i, 2); + QCOMPARE(it2.value().i.use_count(), 2); + QCOMPARE(it3.value().i.use_count(), 1); + m3.insert(QStringLiteral(u"xz"), DomTestClass { std::shared_ptr<int>(new int(16)) }); + it2 = m2.begin(); + it3 = m3.begin(); + QCOMPARE(*it2.value().i, 4); + QCOMPARE(*it3.value().i, 2); + QCOMPARE(it2.value().i.use_count(), 2); + QCOMPARE(it3.value().i.use_count(), 1); + ++it2; + ++it3; + QCOMPARE(*it2.value().i, 8); + QCOMPARE(*it3.value().i, 8); + QCOMPARE(it2.value().i.use_count(), 2); + QCOMPARE(it3.value().i.use_count(), 2); + ++it2; + ++it3; + QVERIFY(it2 == m2.end()); + QVERIFY(it3 != m3.end()); + QCOMPARE(*it3.value().i, 16); + QCOMPARE(it3.value().i.use_count(), 1); + ++it3; + QVERIFY(it3 == m3.end()); + } + } + void testReference() + { + Path p = Path::Root(u"env"); + DomItem ref = env.subReferenceItem(PathEls::Field(u"ref"), p); + QCOMPARE(ref.field(u"referredObjectPath").value().toString(), p.toString()); + QCOMPARE(ref.fields(), + QList<QString>({ QStringLiteral(u"referredObjectPath"), QStringLiteral(u"get") })); + QCOMPARE(ref.field(u"get").internalKind(), DomType::DomEnvironment); + // test stability (cache) + QCOMPARE(ref.field(u"get").internalKind(), DomType::DomEnvironment); + } + void testRefCache() + { + Path refPath = env.canonicalPath().field(u"dummyRef"); + RefCacheEntry e0 = RefCacheEntry::forPath(env, refPath); + QCOMPARE(e0.cached, RefCacheEntry::Cached::None); + bool didAdd1 = RefCacheEntry::addForPath( + env, refPath, RefCacheEntry { RefCacheEntry::Cached::First, {} }); + QVERIFY(didAdd1); + RefCacheEntry e1 = RefCacheEntry::forPath(env, refPath); + QCOMPARE(e1.cached, RefCacheEntry::Cached::All); + QCOMPARE(e1.canonicalPaths.isEmpty(), true); + bool didAdd2 = RefCacheEntry::addForPath( + env, refPath, + RefCacheEntry { RefCacheEntry::Cached::First, { env.canonicalPath() } }, + AddOption::Overwrite); + QVERIFY(didAdd2); + RefCacheEntry e2 = RefCacheEntry::forPath(env, refPath); + QCOMPARE(e2.cached, RefCacheEntry::Cached::First); + QCOMPARE(e2.canonicalPaths.size(), 1); + QCOMPARE(e2.canonicalPaths.first().toString(), env.canonicalPath().toString()); + bool didAdd3 = RefCacheEntry::addForPath( + env, refPath, + RefCacheEntry { RefCacheEntry::Cached::All, + { env.canonicalPath(), tOwner.canonicalPath() } }, + AddOption::Overwrite); + QVERIFY(didAdd3); + RefCacheEntry e3 = RefCacheEntry::forPath(env, refPath); + QCOMPARE(e3.cached, RefCacheEntry::Cached::All); + QCOMPARE(e3.canonicalPaths.size(), 2); + QCOMPARE(e3.canonicalPaths.first().toString(), env.canonicalPath().toString()); + QCOMPARE(e3.canonicalPaths.last().toString(), tOwner.canonicalPath().toString()); + } + void testEnvUniverse() + { + QCOMPARE(env.internalKind(), DomType::DomEnvironment); + QCOMPARE(env.pathFromOwner(), Path()); + QCOMPARE(env.containingObject().internalKind(), DomType::Empty); + QCOMPARE(env.container().internalKind(), DomType::Empty); + QCOMPARE(env.canonicalPath(), Path::Root(u"env")); + QCOMPARE(env.path(u"$env").internalKind(), DomType::DomEnvironment); + QCOMPARE(env.top().internalKind(), DomType::DomEnvironment); + QCOMPARE(env.environment().internalKind(), DomType::DomEnvironment); + QCOMPARE(env.owningItemPtr(), envPtr); + QCOMPARE(env.topPtr(), envPtr); + DomItem univ = env.universe(); + QCOMPARE(univ.internalKind(), DomType::DomUniverse); + QCOMPARE(univ.owningItemPtr(), universePtr); + DomItem univ2 = env.path(u".universe"); + QCOMPARE(univ2.internalKind(), DomType::DomUniverse); + QCOMPARE(univ2.owningItemPtr(), universePtr); + QCOMPARE(univ2.topPtr(), universePtr); + DomItem univ3 = env.field(u"universe"); + QCOMPARE(univ3.internalKind(), DomType::DomUniverse); + } + + void testTOwner() + { + QVERIFY(env.fields().contains(QLatin1String("testOwner"))); + QCOMPARE(tOwner.internalKind(), DomType::MockOwner); + QCOMPARE(tOwner.pathFromOwner(), Path()); + DomItem map = tOwner.field(u"map"); + QCOMPARE(map[u"b"].field(u"val1").value().toInteger(), 12); + QCOMPARE(map[u"b"].container(), map); + QCOMPARE(map[u"b"].container()[u"b"].field(u"val1").value().toInteger(), 12); + QCOMPARE(map[u"b"].containingObject(), tOwner); + DomItem mmap = tOwner.field(u"mmap"); + QCOMPARE(mmap[u"a"].index(0).field(u"val1").value().toInteger(), 14); + QCOMPARE(mmap[u"a"].container(), mmap); + QCOMPARE(mmap[u"a"].container()[u"a"].index(0).field(u"val1").value().toInteger(), 14); + QCOMPARE(mmap[u"a"].containingObject(), tOwner); + QCOMPARE(mmap[u"a"].index(0).container(), mmap[u"a"]); + QCOMPARE(mmap[u"a"].index(0).containingObject(), tOwner); + DomItem list = tOwner.field(u"list"); + QCOMPARE(list[0].field(u"val1").value().toInteger(), 18); + QCOMPARE(list.container(), tOwner); + QCOMPARE(list[0].container(), list); + QCOMPARE(list[0].containingObject(), tOwner); + QCOMPARE(list[0].container()[0].field(u"val1").value().toInteger(), 18); + QCOMPARE(tOwner.containingObject().internalKind(), DomType::DomEnvironment); + QCOMPARE(tOwner.container().internalKind(), DomType::DomEnvironment); + QCOMPARE(tOwner.fields(), + QStringList({ QStringLiteral(u"val1"), QStringLiteral(u"obj1"), + QStringLiteral(u"obj2"), QStringLiteral(u"map"), + QStringLiteral(u"mmap"), QStringLiteral(u"list") })); + auto tOwner2 = env.path(u"$env.testOwner"); + QCOMPARE(tOwner2.internalKind(), DomType::MockOwner); + auto tOwner3 = tOwner.path(u"$env.testOwner"); + QCOMPARE(tOwner3.internalKind(), DomType::MockOwner); + QList<qint64> values; + tOwner.visitTree(Path(), [&values](const Path &p, DomItem i, bool) { + if (i.pathFromOwner() != p) + myErrors() + .error(QStringLiteral(u"unexpected path %1 %2") + .arg(i.pathFromOwner().toString(), p.toString())) + .handle(defaultErrorHandler); + Q_ASSERT(i == i.path(i.canonicalPath())); + if (DomItem v1 = i.path(u".val1")) + values.append(v1.value().toInteger()); + return true; + }); + QCOMPARE(values, QList<qint64>({ 9, 1, 3, 7, 5, 10, 12, 14, 16, 18, 20 })); + } + void testSubObj() + { + auto obj1 = tOwner.field(u"obj1"); + QCOMPARE(obj1.internalKind(), DomType::MockObject); + auto obj1_1 = env.path(u".testOwner.obj1.obj1_2"); + QCOMPARE(obj1_1.internalKind(), DomType::MockObject); + QCOMPARE(obj1_1.field(u"val1").value().toInteger(), 3); + } + void testEquality() + { + auto obj1 = tOwner.field(u"obj1"); + auto obj1_1 = env.path(u".testOwner.obj1.obj1_2"); + QCOMPARE(obj1_1.container(), obj1); + QCOMPARE(obj1_1.environment(), env); + QCOMPARE(obj1_1.owner(), tOwner); + } + void testLoadNoDep() + { +#ifdef Q_OS_ANDROID + QSKIP("Test uncompatible with Android (QTBUG-100171)"); +#endif + 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 + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies, + DomCreationOption::None, univPtr)); + QQmlJS::Dom::DomItem env(envPtr); + QVERIFY(env); + QString testFile1 = baseDir + QLatin1String("/test1.qml"); + DomItem tFile; + 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); + QVERIFY(tFile); + DomItem comp1 = tFile.field(Fields::components).key(QString()).index(0); + QVERIFY(comp1); + DomItem obj1 = comp1.field(Fields::objects).index(0); + QVERIFY(obj1); + + 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() + << i.id() << i2.id() << i.pathFromOwner() << i2.pathFromOwner(); + } + Q_ASSERT(i == i.path(i.canonicalPath())); + Q_ASSERT(!i || i == tFile.path(i.canonicalPath())); + Q_ASSERT(!i || i.containingObject()); + + return true; + }); + + { + DomItem width = obj1.field(Fields::bindings).key(QLatin1String("width")).index(0); + QCOMPARE(width.field(Fields::value).qmlObject(), obj1); + DomItem w = obj1.bindings().key(QLatin1String("width")); + QVERIFY(w.indexes() > 0); + QCOMPARE(w.indexes(), 1); + QVERIFY(w.index(0).as<Binding>()); + QVERIFY(w.index(0).as<Binding>()->scriptExpressionValue()); + QCOMPARE(w.index(0).as<Binding>()->scriptExpressionValue()->code(), u"640"); + PropertyInfo mPInfo; + mPInfo.bindings = { width }; + mPInfo.propertyDefs.append(width); + DomItem wrappedPInfo = obj1.wrapField(Fields::propertyInfos, mPInfo); + QVERIFY(wrappedPInfo); + const SimpleObjectWrapBase *wrappedPInfoPtr = + static_cast<const SimpleObjectWrapBase *>(wrappedPInfo.base()); + QVERIFY(wrappedPInfoPtr); + const PropertyInfo *p1 = + reinterpret_cast<const PropertyInfo *>(wrappedPInfoPtr->m_value.data()); + PropertyInfo p2 = wrappedPInfoPtr->m_value.value<PropertyInfo>(); + QCOMPARE(mPInfo.bindings.size(), 1); + QCOMPARE(mPInfo.propertyDefs.size(), 1); + QCOMPARE(mPInfo.bindings.first().toString(), mPInfo.bindings.first().toString()); + QCOMPARE(mPInfo.propertyDefs.first().toString(), + mPInfo.propertyDefs.first().toString()); + + QCOMPARE(p2.bindings.size(), 1); + QCOMPARE(p2.propertyDefs.size(), 1); + QCOMPARE(p2.bindings.first().toString(), mPInfo.bindings.first().toString()); + QCOMPARE(p2.propertyDefs.first().toString(), mPInfo.propertyDefs.first().toString()); + QCOMPARE(p1->bindings.size(), 1); + QCOMPARE(p1->propertyDefs.size(), 1); + QCOMPARE(p1->bindings.first().toString(), mPInfo.bindings.first().toString()); + QCOMPARE(p1->propertyDefs.first().toString(), mPInfo.propertyDefs.first().toString()); + } + QString bPath = QFileInfo(baseDir).canonicalPath(); + if (!bPath.isEmpty()) { + Path p = tFile.canonicalPath(); + Q_ASSERT(env.path(p)); + env.ownerAs<DomEnvironment>()->removePath(bPath); + Q_ASSERT(!env.path(p)); + } + } + + void testLoadDep() + { +#ifdef Q_OS_ANDROID + QSKIP("Test uncompatible with Android (QTBUG-100171)"); +#endif + 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)); + QQmlJS::Dom::DomItem env(envPtr); + QVERIFY(env); + QString testFile1 = baseDir + QLatin1String("/test1.qml"); + DomItem tFile; + 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); + QVERIFY(tFile); + DomItem comp1 = tFile.field(Fields::components).key(QString()).index(0); + QVERIFY(comp1); + DomItem obj1 = comp1.field(Fields::objects).index(0); + QVERIFY(obj1); + + { + using namespace Qt::StringLiterals; + + QList<DomItem> rect = + obj1.lookup(u"Rectangle"_s, LookupType::Type, LookupOption::Normal); + QList<DomItem> rect2 = + obj1.lookup(u"Rectangle"_s, LookupType::Symbol, LookupOption::Normal); + QList<DomItem> rectAs = + obj1.lookup(u"QQ.Rectangle"_s, LookupType::Symbol, LookupOption::Normal); + + QVERIFY(rect.size() == 1); + QVERIFY(rect2.size() == 1); + QVERIFY(rectAs.size() == 1); + QCOMPARE(rect.first().internalKind(), DomType::Export); + QCOMPARE(rect.first(), rect2.first()); + QCOMPARE(rect.first(), rectAs.first()); + DomItem rect3 = rect.first().proceedToScope(); + QCOMPARE(rect3.internalKind(), DomType::QmlObject); + QList<DomItem> rects; + obj1.resolve( + Path::Current(PathCurrent::Lookup).field(Fields::type).key(u"Rectangle"_s), + [&rects](Path, const DomItem &el) { + rects.append(el); + return true; + }, + {}); + QVERIFY(rects.size() == 1); + for (const DomItem &el : rects) { + QCOMPARE(rect.first(), el); + } + } + { + QString fPath = tFile.canonicalFilePath(); + QString fPath2 = fPath.mid(0, fPath.lastIndexOf(u'/')) % u"/MySingleton.qml"; + Path p2 = Paths::qmlFileObjectPath(fPath2); + 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() + { +#ifdef Q_OS_ANDROID + QSKIP("Test uncompatible with Android (QTBUG-100171)"); +#endif + using namespace Qt::StringLiterals; + + QString testFile1 = baseDir + QLatin1String("/TestImports.qml"); + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + DomItem tFile; + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFile1), + [&tFile](Path, const DomItem &, const DomItem &newIt) { + tFile = newIt.fileObject(); + }); + envPtr->loadPendingDependencies(); + + QVERIFY(tFile); + QList<QmlUri> importedModules; + for (auto &import : tFile.field(Fields::imports).values()) { + if (const Import *importPtr = import.as<Import>()) { + if (!importPtr->implicit) + importedModules.append(importPtr->uri); + } + } + QCOMPARE(importedModules.at(0).moduleUri(), u"QtQuick"_s); + QCOMPARE(importedModules.at(0).directoryString(), u""_s); + QCOMPARE(importedModules.at(1).directoryString(), u"../.."_s); + QCOMPARE(importedModules.at(1).localPath(), u"../.."_s); + QCOMPARE(importedModules.at(1).absoluteLocalPath(), QString()); + QCOMPARE(importedModules.at(1).absoluteLocalPath(u"/bla/bla"_s), u"/bla/bla/../.."); + QCOMPARE(importedModules.at(2).directoryString(), u"../dommerging"_s); + QCOMPARE(importedModules.at(2).localPath(), u"../dommerging"_s); + QCOMPARE(importedModules.at(2).absoluteLocalPath(), QString()); + QCOMPARE(importedModules.at(2).absoluteLocalPath(u"/bla/bla"_s), + u"/bla/bla/../dommerging"); + QCOMPARE(importedModules.at(3).directoryString(), u"C:/some/path"_s); + QCOMPARE(importedModules.at(3).localPath(), u"C:/some/path"_s); + QCOMPARE(importedModules.at(4).directoryString(), u"http://bla.com/"_s); + QCOMPARE(importedModules.at(4).directoryUrl().toString(), u"http://bla.com/"_s); + QCOMPARE(importedModules.at(5).absoluteLocalPath(), u"/absolute/path"_s); + QVERIFY(QmlUri::fromDirectoryString("QtQuick") != importedModules.at(0)); + QCOMPARE(QmlUri::fromUriString("QtQuick"), importedModules.at(0)); + } + void testDeepCopy() + { + QString testFile = baseDir + QLatin1String("/test1.qml"); + + auto envPtr = DomEnvironment::create( + qmltypeDirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + DomItem tFile; // place where to store the loaded file + 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); }); + MutableDomItem copy = f.makeCopy(); + QString dump2; + copy.item().dump([&dump2](QStringView v) { dump2.append(v); }); + QString diff = lineDiff(dump1, dump2, 2); + if (!diff.isEmpty()) + qDebug().nospace().noquote() << diff; + QCOMPARE(dump1, dump2); + QStringList diffs = domCompareStrList(f, copy, FieldFilter::compareFilter()); + 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 = + domCompareStrList(univFile, univFileCopy, FieldFilter::compareFilter()); + if (!univFileDiffs.isEmpty()) + qDebug() << "testDeepCopy.univFileDiffs:" << univFileDiffs; + QVERIFY(univFileDiffs.isEmpty()); + QString bPath = QFileInfo(baseDir).canonicalFilePath(); + if (!bPath.isEmpty()) { + Path p = f.canonicalPath(); + Q_ASSERT(env.path(p)); + env.ownerAs<DomEnvironment>()->removePath(bPath); + Q_ASSERT(!env.path(p)); + } + } + + 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(const DomItem &qmlObj) + { + using namespace Qt::StringLiterals; + + if (const QmlObject *qmlObjPtr = qmlObj.as<QmlObject>()) { + auto pDefs = qmlObjPtr->propertyDefs(); + auto i = pDefs.constBegin(); + while (i != pDefs.constEnd()) { + if (i.value().isAlias()) { + QString propName = i.key(); + DomItem value = qmlObj.bindings().key(propName).index(0).field(Fields::value); + LocallyResolvedAlias rAlias = + qmlObjPtr->resolveAlias(qmlObj, value.ownerAs<ScriptExpression>()); + if (propName.startsWith(u"a")) { + QCOMPARE(rAlias.baseObject.internalKind(), DomType::QmlObject); + switch (propName.last(1).at(0).unicode()) { + case u'i': + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::ResolvedProperty); + QCOMPARE(rAlias.typeName, u"int"_s); + QVERIFY(rAlias.accessedPath.isEmpty()); + QCOMPARE(rAlias.localPropertyDef.internalKind(), + DomType::PropertyDefinition); + break; + case u'r': + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::ResolvedProperty); + QCOMPARE(rAlias.typeName, u"real"_s); + QVERIFY(rAlias.accessedPath.isEmpty()); + QCOMPARE(rAlias.localPropertyDef.internalKind(), + DomType::PropertyDefinition); + break; + case u'I': + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::ResolvedObject); + QCOMPARE(rAlias.typeName, u"Item"_s); + QCOMPARE(rAlias.accessedPath, QStringList { u"objectName"_s }); + QVERIFY(!rAlias.localPropertyDef); + break; + case u'q': + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::ResolvedObject); + QCOMPARE(rAlias.typeName, u"QtObject"_s); + QCOMPARE(rAlias.accessedPath, QStringList { u"objectName"_s }); + QVERIFY(!rAlias.localPropertyDef); + break; + case u'Q': + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::ResolvedObject); + QCOMPARE(rAlias.typeName, u"QtObject"_s); + QCOMPARE(rAlias.accessedPath, QStringList { u"objectName"_s }); + QVERIFY(rAlias.localPropertyDef); + break; + default: + Q_ASSERT(false); + } + } else if (propName.startsWith(u"loop")) { + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::Loop); + } else if (propName.startsWith(u"tooDeep")) { + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::TooDeep); + } else if (propName.startsWith(u"invalid")) { + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::Invalid); + } else if (propName.startsWith(u"objRef")) { + QCOMPARE(rAlias.status, LocallyResolvedAlias::Status::ResolvedObject); + } else { + Q_ASSERT(false); + } + } + ++i; + } + for (const DomItem &obj : qmlObj.children().values()) { + if (obj.as<QmlObject>()) + checkAliases(obj); + } + } + } + + void testAliasResolve_data() + { + QTest::addColumn<QString>("inFile"); + + QTest::newRow("aliasProperties") << QStringLiteral(u"aliasProperties.qml"); + QTest::newRow("invalidAliasProperties") << QStringLiteral(u"invalidAliasProperties.qml"); + } + void testAliasResolve() + { + using namespace Qt::StringLiterals; + + QFETCH(QString, inFile); + QString testFile1 = baseDir + u"/"_s + inFile; + auto envPtr = DomEnvironment::create( + QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + DomItem tFile; + 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; + } + 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 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)); + } + +private: + QString baseDir; + QStringList qmltypeDirs; + std::shared_ptr<DomUniverse> universePtr; + std::shared_ptr<DomEnvironment> envPtr; + DomItem env; + std::shared_ptr<MockOwner> testOwnerPtr; + DomItem tOwner; +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif diff --git a/tests/auto/qmldom/errormessage/CMakeLists.txt b/tests/auto/qmldom/errormessage/CMakeLists.txt index 4a8f5043b9..7c82876827 100644 --- a/tests/auto/qmldom/errormessage/CMakeLists.txt +++ b/tests/auto/qmldom/errormessage/CMakeLists.txt @@ -1,15 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from errormessage.pro. ##################################################################### ## 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.cpp tst_qmldomerrormessage.h DEFINES QT_DEPRECATED_WARNINGS - PUBLIC_LIBRARIES - Qt::QmlDevToolsPrivate + LIBRARIES Qt::QmlDomPrivate ) diff --git a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp index e36278771d..2c2007c119 100644 --- a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp +++ b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.cpp @@ -1,40 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -**/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmldomerrormessage.h" #include <QtQmlDom/private/qqmldomerrormessage_p.h> #include <QtTest/QtTest> @@ -62,35 +29,31 @@ void registerMyError() { static auto myError1 = ErrorMessage::msg("my.company.error1", myErrors().warning(u"Error number 1")); static auto myError2 = ErrorMessage::msg("my.company.error2", myErrors().error(u"Error number 2 on %1")); -class TestErrorMessage: public QObject +void TestErrorMessage::testError() { - Q_OBJECT -private slots: - void testError() - { - registerMyError(); - auto err0 = ErrorMessage::load(myError0); - QCOMPARE(err0.errorId, QLatin1String(myError0)); - QCOMPARE(err0.message, dumperToString(u"Error number 0")); - QCOMPARE(err0.level, ErrorLevel::Warning); - auto err1 = ErrorMessage::load(QLatin1String("my.company.error1")); - QCOMPARE(err1.errorId, myError1); - QCOMPARE(err1.message, dumperToString(u"Error number 1")); - QCOMPARE(err1.level, ErrorLevel::Warning); - auto err1bis = ErrorMessage::load("my.company.error1"); - QCOMPARE(err1bis.errorId, myError1); - QCOMPARE(err1bis.message, dumperToString(u"Error number 1")); - QCOMPARE(err1bis.level, ErrorLevel::Warning); - auto err2 = ErrorMessage::load(myError2, QLatin1String("extra info")); - QCOMPARE(err2.errorId, myError2); - QCOMPARE(err2.message, dumperToString(u"Error number 2 on extra info")); - QCOMPARE(err2.level, ErrorLevel::Error); - } -}; + registerMyError(); + auto err0 = ErrorMessage::load(myError0); + QCOMPARE(err0.errorId, QLatin1String(myError0)); + QCOMPARE(err0.message, dumperToString(u"Error number 0")); + QCOMPARE(err0.level, ErrorLevel::Warning); + auto err1 = ErrorMessage::load(QLatin1String("my.company.error1")); + QCOMPARE(err1.errorId, myError1); + QCOMPARE(err1.message, dumperToString(u"Error number 1")); + QCOMPARE(err1.level, ErrorLevel::Warning); + auto err1bis = ErrorMessage::load("my.company.error1"); + QCOMPARE(err1bis.errorId, myError1); + QCOMPARE(err1bis.message, dumperToString(u"Error number 1")); + QCOMPARE(err1bis.level, ErrorLevel::Warning); + auto err2 = ErrorMessage::load(myError2, QLatin1String("extra info")); + QCOMPARE(err2.errorId, myError2); + QCOMPARE(err2.message, dumperToString(u"Error number 2 on extra info")); + QCOMPARE(err2.level, ErrorLevel::Error); +} } } QT_END_NAMESPACE +#ifndef NO_QTEST_MAIN QTEST_MAIN(QQmlJS::Dom::TestErrorMessage) -#include "tst_qmldomerrormessage.moc" +#endif diff --git a/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h new file mode 100644 index 0000000000..31628ef52d --- /dev/null +++ b/tests/auto/qmldom/errormessage/tst_qmldomerrormessage.h @@ -0,0 +1,20 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtQmlDom/qqmldom_global.h> +#include <QtTest/QtTest> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT TestErrorMessage: public QObject +{ + Q_OBJECT +private slots: + void testError(); +}; + +} // Dom +} // QQmlJS +QT_END_NAMESPACE diff --git a/tests/auto/qmldom/merging/CMakeLists.txt b/tests/auto/qmldom/merging/CMakeLists.txt new file mode 100644 index 0000000000..a33df96216 --- /dev/null +++ b/tests/auto/qmldom/merging/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from domitem.pro. + +##################################################################### +## 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}/.. + domdata/dommerging) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_dommerging + SOURCES + tst_dommerging.cpp tst_dommerging.h + DEFINES + QT_DEPRECATED_WARNINGS + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/../domdata" + LIBRARIES + Qt::Core + Qt::QmlDomPrivate + Qt::Test + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qmldomitem CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/domdata" +) diff --git a/tests/auto/qmldom/merging/tst_dommerging.cpp b/tests/auto/qmldom/merging/tst_dommerging.cpp new file mode 100644 index 0000000000..1fa994b1a3 --- /dev/null +++ b/tests/auto/qmldom/merging/tst_dommerging.cpp @@ -0,0 +1,5 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// 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 new file mode 100644 index 0000000000..59937b279e --- /dev/null +++ b/tests/auto/qmldom/merging/tst_dommerging.h @@ -0,0 +1,102 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_DOMMERGING_H +#define TST_DOMMERGING_H +#include <QtQmlDom/private/qqmldomitem_p.h> +#include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQmlDom/private/qqmldomastdumper_p.h> + +#include <QtTest/QtTest> +#include <QCborValue> +#include <QDebug> +#include <QLibraryInfo> + +#include <memory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class QMLDOM_EXPORT TestDomMerging : public QObject +{ + Q_OBJECT +public: + static ErrorGroups myErrors() + { + static ErrorGroups res { { NewErrorGroup("tests"), NewErrorGroup("domitem") } }; + return res; + } + +private slots: + void initTestCase() + { + QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/dommerging"); + QStringList qmltypeDirs = + QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); + + auto envPtr = std::shared_ptr<QQmlJS::Dom::DomEnvironment>(new QQmlJS::Dom::DomEnvironment( + qmltypeDirs, + DomEnvironment::Option::SingleThreaded | DomEnvironment::Option::NoDependencies)); + QString testFile1 = baseDir + QLatin1String("/test1.qml"); + + 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); + QVERIFY(tFile); + } + + void testReformat() + { + DomItem comp1 = tFile.field(Fields::components).key(QString()).index(0); + QVERIFY(comp1); + DomItem obj1 = comp1.field(Fields::objects).index(0); + QVERIFY(obj1); + DomItem width = obj1.field(Fields::bindings).key(QLatin1String("width")).index(0); + DomItem w = obj1.bindings().key(QLatin1String("width")); + QVERIFY(w.length() > 0); + QCOMPARE(w.length(), 1); + DomItem exp = width.field(Fields::value); + if (std::shared_ptr<ScriptExpression> expPtr = exp.ownerAs<ScriptExpression>()) { + QCOMPARE(expPtr->code(), u"{ height *3/4 }"); + } + MutableDomItem myobj(obj1); + PropertyDefinition pDef; + pDef.name = QLatin1String("foo"); + pDef.typeName = QLatin1String("int"); + MutableDomItem propertyDef = myobj.addPropertyDef(pDef, AddOption::Overwrite); + QVERIFY(propertyDef); + MutableDomItem binding = myobj.addBinding( + Binding(QLatin1String("foo"), + std::shared_ptr<ScriptExpression>(new ScriptExpression( + QLatin1String("42"), + ScriptExpression::ExpressionType::BindingExpression))), + AddOption::Overwrite); + QVERIFY(binding); + QCOMPARE(binding.item().field(Fields::value).field(Fields::errors).length(), 0); + std::shared_ptr<ScriptExpression> expr = + binding.item().field(Fields::value).ownerAs<ScriptExpression>(); + QVERIFY(expr && expr->ast()); + QCOMPARE(expr->ast()->kind, AST::Node::Kind_NumericLiteral); + MutableDomItem pInfo = myobj.field(Fields::propertyInfos).key(QLatin1String("foo")); + // dumperToQDebug([pInfo](Sink s){ pInfo.dump(s); }); + QCOMPARE(propertyDef.item(), pInfo.field(Fields::propertyDefs).index(0).item()); + QCOMPARE(binding, pInfo.field(Fields::bindings).index(0)); + } + +private: + std::shared_ptr<DomEnvironment> envPtr; + DomItem env; + DomItem tFile; +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif diff --git a/tests/auto/qmldom/path/CMakeLists.txt b/tests/auto/qmldom/path/CMakeLists.txt index 4c4109dd02..bf3e5d20f5 100644 --- a/tests/auto/qmldom/path/CMakeLists.txt +++ b/tests/auto/qmldom/path/CMakeLists.txt @@ -1,15 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from path.pro. ##################################################################### ## 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.cpp tst_qmldompath.h DEFINES QT_DEPRECATED_WARNINGS - PUBLIC_LIBRARIES - Qt::QmlDevToolsPrivate + LIBRARIES Qt::QmlDomPrivate ) diff --git a/tests/auto/qmldom/path/tst_qmldompath.cpp b/tests/auto/qmldom/path/tst_qmldompath.cpp index b11abe6283..54a634e476 100644 --- a/tests/auto/qmldom/path/tst_qmldompath.cpp +++ b/tests/auto/qmldom/path/tst_qmldompath.cpp @@ -1,229 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -**/ -#include <QtQmlDom/private/qqmldompath_p.h> -#include <QtQmlDom/private/qqmldomitem_p.h> - -#include <QtTest/QtTest> - -QT_BEGIN_NAMESPACE -namespace QQmlJS { -namespace Dom { -namespace PathEls { - -class TestPaths: public QObject { - Q_OBJECT -public: - void testPathInternals(Path p1) - { - QCOMPARE(p1.component(0).kind(), Kind::Root); - QCOMPARE(p1.component(1).kind(), Kind::Current); - - Path p11 = Path::field(u"test"); - QString s = QLatin1String("test"); - Path p2 = Path::field(s); - Path p3 = Path::field(QLatin1String("test")); - QCOMPARE(p11, p2); - QCOMPARE(p11, p3); - QVERIFY(p11.m_data->strData.isEmpty()); - QCOMPARE(p2.m_data->strData.length(), 1); - QCOMPARE(p2.m_data->strData.first(), s); - QCOMPARE(p3.m_data->strData.length(), 1); - QCOMPARE(p3.m_data->strData.first(), s); - } - -private slots: - void pathComponentTestInternalAlloc() { - PathComponent c; - QCOMPARE(c.kind(), Kind::Empty); - PathComponent c1{Current()}; - QCOMPARE(c1.kind(), Kind::Current); - QVERIFY(c!=c1); - QVERIFY(c<c1); - QVERIFY(c1>c); - PathComponent c1_1{Current(PathCurrent::Ids)}; - QCOMPARE(c1_1.kind(), Kind::Current); - QVERIFY(c1 != c1_1); - QVERIFY(c < c1_1); - QCOMPARE(c1_1.name(), QLatin1String("@ids")); - PathComponent c1_2{Current(u"ids")}; - QCOMPARE(c1_1, c1_2); - PathComponent c2 = c1; - QCOMPARE(c2.kind(), Kind::Current); - QCOMPARE(c2, c1); - PathComponent c3; - QCOMPARE(c, c3); - QCOMPARE(c3.kind(), Kind::Empty); - c3 = c1; - QCOMPARE(c3.kind(), Kind::Current); - QCOMPARE(c3, c1); - PathComponent c4{Field(u"bla")}; - QCOMPARE(c4.kind(), Kind::Field); - QCOMPARE(c4.name(), QLatin1String("bla")); - auto c5=PathComponent(Index(42)); - QCOMPARE(c5.kind(), Kind::Index); - QCOMPARE(c5.index(), 42); - auto c6=PathComponent(Key(u"bla")); - QCOMPARE(c6.kind(), Kind::Key); - QCOMPARE(c6.name(), QLatin1String("bla")); - auto c7=PathComponent(Key(u" ugly\n \t \\string\"'bla")); - QCOMPARE(c7.kind(), Kind::Key); - QCOMPARE(c7.name(), QLatin1String(" ugly\n \t \\string\"'bla")); - auto c8=PathComponent(Root(u"pippo")); - QCOMPARE(c8.kind(), Kind::Root); - QCOMPARE(c8.name(), QLatin1String("$pippo")); - auto c8_1=PathComponent(Root(PathRoot::Env)); - QCOMPARE(c8_1.kind(), Kind::Root); - QCOMPARE(c8_1.name(), QLatin1String("$env")); - auto c8_2=PathComponent(Root(u"env")); - QCOMPARE(c8_1, c8_2); - auto c9=PathComponent(Current(u"ippo")); - QCOMPARE(c9.kind(), Kind::Current); - QCOMPARE(c9.name(), QLatin1String("@ippo")); - auto c10=PathComponent(Any()); - QCOMPARE(c10.kind(), Kind::Any); - QVERIFY(c9!=c10); - auto c11=PathComponent(Filter([](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")); - 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 - QVERIFY(c11 != c13); - QVERIFY(c13 != c14); - QVERIFY(c11 != c14); - QCOMPARE(c14, c14); - QCOMPARE(c14, c15); // same description (without < at the beginning) assumes same function even if different - } - - void testPaths() { - Path p; - QCOMPARE(p.length(), 0); - QCOMPARE(p.length(), 0); - Path p0 = Path::root(); - QCOMPARE(p0[0].headKind(), Kind::Root); - QCOMPARE(p0.length(), 1); - Path p1 = p0.subCurrent(); - QCOMPARE(p1.length(), 2); - testPathInternals(p1); - QCOMPARE(p1[0].headKind(), Kind::Root); - QCOMPARE(p1[1].headKind(), Kind::Current); - auto p2 = p1.subField(u"aa"); - QCOMPARE(p2[2].headKind(), Kind::Field); - auto p3 = p2.subIndex(4); - QCOMPARE(p3[3].headKind(), Kind::Index); - auto p4 = p3.subKey("bla"); - QCOMPARE(p4[4].headKind(), Kind::Key); - auto p5 = p4.subAny(); - QCOMPARE(p5[5].headKind(), Kind::Any); - auto p6 = p5.subEmpty(); - QCOMPARE(p6[6].headKind(), Kind::Empty); - auto rString = u"$.@.aa[4][\"bla\"][*]."; - QCOMPARE(p6.toString(), rString); - auto p7 = p6.subFilter([](DomItem){ return true; }, u"true"); - auto p7Str = p7.toString(); - QCOMPARE(p7Str, u"$.@.aa[4][\"bla\"][*].[?(true)]"); - auto p8 = p7.dropTail(); - QCOMPARE(p8.length(), 7); - QCOMPARE(p8.toString(), rString); - QCOMPARE(p8, p6); - auto p9 = Path::fromString(rString); - QCOMPARE(p9.length(), 7); - auto p9Str = p9.toString(); - QCOMPARE(p9Str, rString); - QCOMPARE(p9, p6); - auto p10 = p6.dropFront(); - QCOMPARE(p10.length(), 6); - auto p10Str = p10.toString(); - auto r2Str = u"@.aa[4][\"bla\"][*]."; - QCOMPARE(p10Str, r2Str); - auto p11 = Path::fromString(r2Str); - auto p11Str = p11.toString(); - QCOMPARE(p11Str, r2Str); - QCOMPARE(p10, p11); - auto p12 = p7.mid(1,6); - auto p12Str = p12.toString(); - QCOMPARE(p12Str, r2Str); - QCOMPARE(p10, p12); - } - - void testPathSplit() - { - QList<Path> paths({Path(), - Path::root(PathRoot::Env).subField(u"pippo").subKey(u"pluto").subIndex(4), - Path::root(PathRoot::Env).subField(u"pippo").subKey(u"pluto"), - Path::root(PathRoot::Env).subField(u"pippo"), - Path::root(PathRoot::Env).subField(u"pippo").subField(u"pp"), - Path::root(PathRoot::Env), - Path::field(u"pippo").subIndex(4), - Path::field(u"pippo").subKey(u"pluto").subIndex(4), - Path::field(u"pippo").subKey(u"pluto"), - Path::field(u"pippo"), - Path::field(u"pippo").subField(u"pp"), - Path::index(4), - Path::key(u"zz") - }); - foreach (Path p, paths) { - Source s = p.split(); - QCOMPARE(p, s.pathToSource.subPath(s.pathFromSource)); - if (!s.pathFromSource) - QVERIFY(!s.pathToSource); - } - QCOMPARE(paths.at(1).split().pathToSource, Path::root(PathRoot::Env)); - QCOMPARE(paths.at(2).split().pathToSource, Path::root(PathRoot::Env)); - QCOMPARE(paths.at(3).split().pathToSource, Path::root(PathRoot::Env)); - QCOMPARE(paths.at(4).split().pathToSource, Path::root(PathRoot::Env).subField(u"pippo")); - QVERIFY(!paths.at(5).split().pathToSource); - QVERIFY(!paths.at(6).split().pathToSource); - QVERIFY(!paths.at(7).split().pathToSource); - QVERIFY(!paths.at(8).split().pathToSource); - QVERIFY(!paths.at(9).split().pathToSource); - QCOMPARE(paths.at(10).split().pathToSource, Path::field(u"pippo")); - QVERIFY(!paths.at(11).split().pathToSource); - QVERIFY(!paths.at(12).split().pathToSource); - } -}; - -} // namespace PathEls -} // namespace Dom -} // namespace QQmlJS -QT_END_NAMESPACE +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "tst_qmldompath.h" QTEST_MAIN(QQmlJS::Dom::PathEls::TestPaths) -#include "tst_qmldompath.moc" diff --git a/tests/auto/qmldom/path/tst_qmldompath.h b/tests/auto/qmldom/path/tst_qmldompath.h new file mode 100644 index 0000000000..f463b93164 --- /dev/null +++ b/tests/auto/qmldom/path/tst_qmldompath.h @@ -0,0 +1,204 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QMLDOMPATH_H +#define TST_QMLDOMPATH_H +#include <QtQmlDom/private/qqmldompath_p.h> +#include <QtQmlDom/private/qqmldomitem_p.h> + +#include <QtTest/QtTest> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { +namespace PathEls { + +class TestPaths: public QObject { + Q_OBJECT +public: + void testPathInternals(const Path &p1) + { + QCOMPARE(p1.component(0).kind(), Kind::Root); + QCOMPARE(p1.component(1).kind(), Kind::Current); + + Path p11 = Path::Field(u"test"); + QString s = QLatin1String("test"); + Path p2 = Path::Field(s); + Path p3 = Path::Field(QLatin1String("test")); + QCOMPARE(p11, p2); + QCOMPARE(p11, p3); + QVERIFY(p11.m_data->strData.isEmpty()); + QCOMPARE(p2.m_data->strData.size(), 1); + QCOMPARE(p2.m_data->strData.first(), s); + QCOMPARE(p3.m_data->strData.size(), 1); + QCOMPARE(p3.m_data->strData.first(), s); + } + +private slots: + void pathComponentTestInternalAlloc() { + PathComponent c; + QCOMPARE(c.kind(), Kind::Empty); + PathComponent c1{Current()}; + QCOMPARE(c1.kind(), Kind::Current); + QVERIFY(c!=c1); + QVERIFY(c<c1); + QVERIFY(c1>c); + PathComponent c1_1{Current(PathCurrent::Ids)}; + QCOMPARE(c1_1.kind(), Kind::Current); + QVERIFY(c1 != c1_1); + QVERIFY(c < c1_1); + QCOMPARE(c1_1.name(), QLatin1String("@ids")); + PathComponent c1_2{Current(u"ids")}; + QCOMPARE(c1_1, c1_2); + PathComponent c2 = c1; + QCOMPARE(c2.kind(), Kind::Current); + QCOMPARE(c2, c1); + PathComponent c3; + QCOMPARE(c, c3); + QCOMPARE(c3.kind(), Kind::Empty); + c3 = c1; + QCOMPARE(c3.kind(), Kind::Current); + QCOMPARE(c3, c1); + PathComponent c4{Field(u"bla")}; + QCOMPARE(c4.kind(), Kind::Field); + QCOMPARE(c4.name(), QLatin1String("bla")); + auto c5=PathComponent(Index(42)); + QCOMPARE(c5.kind(), Kind::Index); + QCOMPARE(c5.index(), 42); + auto c6 = PathComponent(Key(QStringLiteral(u"bla"))); + QCOMPARE(c6.kind(), Kind::Key); + QCOMPARE(c6.name(), QLatin1String("bla")); + auto c7 = PathComponent(Key(QStringLiteral(u" ugly\n \t \\string\"'bla"))); + QCOMPARE(c7.kind(), Kind::Key); + QCOMPARE(c7.name(), QLatin1String(" ugly\n \t \\string\"'bla")); + auto c8=PathComponent(Root(u"pippo")); + QCOMPARE(c8.kind(), Kind::Root); + QCOMPARE(c8.name(), QLatin1String("$pippo")); + auto c8_1=PathComponent(Root(PathRoot::Env)); + QCOMPARE(c8_1.kind(), Kind::Root); + QCOMPARE(c8_1.name(), QLatin1String("$env")); + auto c8_2=PathComponent(Root(u"env")); + QCOMPARE(c8_1, c8_2); + auto c9=PathComponent(Current(u"ippo")); + QCOMPARE(c9.kind(), Kind::Current); + QCOMPARE(c9.name(), QLatin1String("@ippo")); + auto c10=PathComponent(Any()); + QCOMPARE(c10.kind(), Kind::Any); + QVERIFY(c9!=c10); + auto c11=PathComponent(Filter([](const DomItem &){ return true; })); + auto c12=c11; + 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 + QVERIFY(c11 != c13); + QVERIFY(c13 != c14); + QVERIFY(c11 != c14); + QCOMPARE(c14, c14); + QCOMPARE(c14, c15); // same description (without < at the beginning) assumes same function even if different + } + + void testPaths() { + Path p; + QCOMPARE(p.length(), 0); + QCOMPARE(p.length(), 0); + Path p0 = Path::Root(); + QCOMPARE(p0[0].headKind(), Kind::Root); + QCOMPARE(p0.length(), 1); + Path p1 = p0.current(); + QCOMPARE(p1.length(), 2); + testPathInternals(p1); + QCOMPARE(p1[0].headKind(), Kind::Root); + QCOMPARE(p1[1].headKind(), Kind::Current); + auto p2 = p1.field(u"aa"); + QCOMPARE(p2[2].headKind(), Kind::Field); + auto p2b = p1.appendComponent(PathEls::Field(u"aa")); + QCOMPARE(p2b.length(), 3); + QCOMPARE(p2b[2].headKind(), Kind::Field); + QCOMPARE(p2b, p2); + auto p3a = p2.appendComponent(PathEls::Index(4)); + QCOMPARE(p3a[3].headKind(), Kind::Index); + auto p3 = p2.index(4); + QCOMPARE(p3.length(), 4); + QCOMPARE(p3[3].headKind(), Kind::Index); + QCOMPARE(p3, p3a); + auto p4 = p3.key("bla"); + QCOMPARE(p4[4].headKind(), Kind::Key); + auto p5 = p4.any(); + QCOMPARE(p5[5].headKind(), Kind::Any); + auto p6 = p5.empty(); + QCOMPARE(p6[6].headKind(), Kind::Empty); + auto rString = u"$.@.aa[4][\"bla\"][*]."; + QCOMPARE(p6.toString(), rString); + 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(); + QCOMPARE(p8.length(), 7); + QCOMPARE(p8.toString(), rString); + QCOMPARE(p8, p6); + auto p9 = Path::fromString(rString); + QCOMPARE(p9.length(), 7); + auto p9Str = p9.toString(); + QCOMPARE(p9Str, rString); + QCOMPARE(p9, p6); + auto p10 = p6.dropFront(); + QCOMPARE(p10.length(), 6); + auto p10Str = p10.toString(); + auto r2Str = u"@.aa[4][\"bla\"][*]."; + QCOMPARE(p10Str, r2Str); + auto p11 = Path::fromString(r2Str); + auto p11Str = p11.toString(); + QCOMPARE(p11Str, r2Str); + QCOMPARE(p10, p11); + auto p12 = p7.mid(1,6); + auto p12Str = p12.toString(); + QCOMPARE(p12Str, r2Str); + QCOMPARE(p10, p12); + } + + void testPathSplit() + { + 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"), + Path::Root(PathRoot::Env).field(u"pippo").field(u"pp"), + Path::Root(PathRoot::Env), + Path::Field(u"pippo").index(4), + Path::Field(u"pippo").key(u"pluto").index(4), + Path::Field(u"pippo").key(u"pluto"), + Path::Field(u"pippo"), + Path::Field(u"pippo").field(u"pp"), + Path::Index(4), + Path::Key(u"zz") + }); + for (const Path &p : paths) { + Source s = p.split(); + QCOMPARE(p, s.pathToSource.path(s.pathFromSource)); + if (!s.pathFromSource) + QVERIFY(!s.pathToSource); + } + QCOMPARE(paths.at(1).split().pathToSource, Path::Root(PathRoot::Env)); + QCOMPARE(paths.at(2).split().pathToSource, Path::Root(PathRoot::Env)); + QCOMPARE(paths.at(3).split().pathToSource, Path::Root(PathRoot::Env)); + QCOMPARE(paths.at(4).split().pathToSource, Path::Root(PathRoot::Env).field(u"pippo")); + QVERIFY(!paths.at(5).split().pathToSource); + QVERIFY(!paths.at(6).split().pathToSource); + QVERIFY(!paths.at(7).split().pathToSource); + QVERIFY(!paths.at(8).split().pathToSource); + QVERIFY(!paths.at(9).split().pathToSource); + QCOMPARE(paths.at(10).split().pathToSource, Path::Field(u"pippo")); + QVERIFY(!paths.at(11).split().pathToSource); + QVERIFY(!paths.at(12).split().pathToSource); + } +}; + +} // namespace PathEls +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif diff --git a/tests/auto/qmldom/reformatter/CMakeLists.txt b/tests/auto/qmldom/reformatter/CMakeLists.txt new file mode 100644 index 0000000000..1b8cfb0d8a --- /dev/null +++ b/tests/auto/qmldom/reformatter/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## 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}/.. + domdata/reformatter/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_reformatter + SOURCES + tst_reformatter.cpp tst_reformatter.h + DEFINES + QT_DEPRECATED_WARNINGS + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/../domdata" + LIBRARIES + Qt::Core + Qt::QmlDomPrivate + Qt::Test + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_reformatter CONDITION ANDROID OR IOS + DEFINES + QT_REFORMATTERTEST_DATADIR=":/domdata" +) diff --git a/tests/auto/qmldom/reformatter/tst_reformatter.cpp b/tests/auto/qmldom/reformatter/tst_reformatter.cpp new file mode 100644 index 0000000000..00d26bc55b --- /dev/null +++ b/tests/auto/qmldom/reformatter/tst_reformatter.cpp @@ -0,0 +1,5 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// 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 new file mode 100644 index 0000000000..31d80097c1 --- /dev/null +++ b/tests/auto/qmldom/reformatter/tst_reformatter.h @@ -0,0 +1,774 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QMLDOMCODEFORMATTER_H +#define TST_QMLDOMCODEFORMATTER_H +#include <QtQmlDom/private/qqmldomlinewriter_p.h> +#include <QtQmlDom/private/qqmldomindentinglinewriter_p.h> +#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> +#include <QDebug> +#include <QLibraryInfo> + +#include <memory> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +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() + { + QTest::addColumn<QString>("inFile"); + QTest::addColumn<QString>("outFile"); + + QTest::newRow("file1") << QStringLiteral(u"file1.qml") << QStringLiteral(u"file1.qml"); + QTest::newRow("file1 unindented") + << QStringLiteral(u"file1Unindented.qml") << QStringLiteral(u"file1.qml"); + } + + void reindent() + { + QFETCH(QString, inFile); + QFETCH(QString, outFile); + + QFile fIn(QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter/") + inFile); + if (!fIn.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "could not open file" << inFile; + return; + } + QFile fOut(QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter/") + outFile); + if (!fOut.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "could not open file" << outFile; + return; + } + QTextStream in(&fIn); + QTextStream out(&fOut); + QString resultStr; + QTextStream res(&resultStr); + QString line = in.readLine(); + IndentingLineWriter lw([&res](QStringView s) { res << s; }, QLatin1String("*testStream*")); + QList<SourceLocation *> sourceLocations; + while (!line.isNull()) { + SourceLocation *loc = new SourceLocation; + sourceLocations.append(loc); + lw.write(line, loc); + lw.write(u"\n"); + line = in.readLine(); + } + lw.eof(); + res.flush(); + QString fullRes = resultStr; + res.seek(0); + line = out.readLine(); + QString resLine = res.readLine(); + int iLoc = 0; + int nextLoc = 0; + while (!line.isNull() && !resLine.isNull()) { + QCOMPARE(resLine, line); + if (iLoc == nextLoc && iLoc < sourceLocations.size()) { + QString l2 = + fullRes.mid(sourceLocations[iLoc]->offset, sourceLocations[iLoc]->length); + if (!l2.contains(QLatin1Char('\n'))) { + QCOMPARE(l2, line); + } else { + qDebug() << "skip checks of multiline location (line was split)" << l2; + iLoc -= l2.count(QLatin1Char('\n')); + } + ++nextLoc; + } else { + qDebug() << "skip multiline recover"; + } + ++iLoc; + line = out.readLine(); + resLine = res.readLine(); + } + QCOMPARE(resLine.isNull(), line.isNull()); + for (auto sLoc : sourceLocations) + delete sLoc; + } + + void lineByLineReformatter_data() + { + QTest::addColumn<QString>("inFile"); + QTest::addColumn<QString>("outFile"); + QTest::addColumn<LineWriterOptions>("options"); + LineWriterOptions defaultOptions; + LineWriterOptions noReorderOptions; + noReorderOptions.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; + + QTest::newRow("file1") << QStringLiteral(u"file1.qml") + << QStringLiteral(u"file1Reformatted.qml") << defaultOptions; + + QTest::newRow("file2") << QStringLiteral(u"file2.qml") + << QStringLiteral(u"file2Reformatted.qml") << defaultOptions; + + QTest::newRow("commentedFile") + << QStringLiteral(u"commentedFile.qml") + << QStringLiteral(u"commentedFileReformatted.qml") << defaultOptions; + + QTest::newRow("required") << QStringLiteral(u"required.qml") + << QStringLiteral(u"requiredReformatted.qml") << defaultOptions; + + QTest::newRow("inline") << QStringLiteral(u"inline.qml") + << QStringLiteral(u"inlineReformatted.qml") << defaultOptions; + + QTest::newRow("spread") << QStringLiteral(u"spread.qml") + << QStringLiteral(u"spreadReformatted.qml") << defaultOptions; + + QTest::newRow("template") << QStringLiteral(u"template.qml") + << QStringLiteral(u"templateReformatted.qml") << defaultOptions; + + QTest::newRow("typeAnnotations") + << QStringLiteral(u"typeAnnotations.qml") + << QStringLiteral(u"typeAnnotationsReformatted.qml") << defaultOptions; + + QTest::newRow("file1NoReorder") + << QStringLiteral(u"file1.qml") << QStringLiteral(u"file1Reformatted2.qml") + << noReorderOptions; + } + + void lineByLineReformatter() + { + QFETCH(QString, inFile); + QFETCH(QString, outFile); + QFETCH(LineWriterOptions, options); + + QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter"); + QStringList qmltypeDirs = + QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); + auto envPtr = DomEnvironment::create( + qmltypeDirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + QString testFilePath = baseDir + QLatin1Char('/') + inFile; + DomItem tFile; + 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); + + QString resultStr; + QTextStream res(&resultStr); + IndentingLineWriter lw([&res](QStringView s) { res << s; }, QLatin1String("*testStream*"), + options); + OutWriter ow(lw); + DomItem qmlFile = tFile.field(Fields::currentItem); + qmlFile.writeOut(ow); + lw.eof(); + res.flush(); + QString fullRes = resultStr; + res.seek(0); + QFile fOut(baseDir + QLatin1Char('/') + outFile); + if (!fOut.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "could not open file" << outFile; + return; + } + QTextStream out(&fOut); + QString line = out.readLine(); + QString resLine = res.readLine(); + auto writeReformatted = [fullRes]() { + qDebug().noquote().nospace() << "Reformatted output:\n" + << "-----------------\n" + << fullRes << "-----------------\n"; + }; + while (!line.isNull() && !resLine.isNull()) { + if (resLine != line) + writeReformatted(); + QCOMPARE(resLine, line); + line = out.readLine(); + resLine = res.readLine(); + } + if (resLine.isNull() != line.isNull()) { + writeReformatted(); + qDebug() << "reformatted at end" << resLine.isNull() << resLine + << "reference at end:" << line.isNull() << line; + } + QCOMPARE(resLine.isNull(), line.isNull()); + } + + void manualReformatter_data() + { + LineWriterOptions noReorderOptions; + QTest::addColumn<QString>("inFile"); + QTest::addColumn<QString>("outFile"); + QTest::addColumn<LineWriterOptions>("options"); + LineWriterOptions defaultOptions; + + noReorderOptions.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; + + QTest::newRow("file1") << QStringLiteral(u"file1.qml") + << QStringLiteral(u"file1Reformatted.qml") << defaultOptions; + + QTest::newRow("file2") << QStringLiteral(u"file2.qml") + << QStringLiteral(u"file2Reformatted.qml") << defaultOptions; + + QTest::newRow("commentedFile") + << QStringLiteral(u"commentedFile.qml") + << QStringLiteral(u"commentedFileReformatted2.qml") << defaultOptions; + + QTest::newRow("required") << QStringLiteral(u"required.qml") + << QStringLiteral(u"requiredReformatted2.qml") << defaultOptions; + + QTest::newRow("inline") << QStringLiteral(u"inline.qml") + << QStringLiteral(u"inlineReformatted.qml") << defaultOptions; + + QTest::newRow("spread") << QStringLiteral(u"spread.qml") + << QStringLiteral(u"spreadReformatted.qml") << defaultOptions; + + QTest::newRow("template") << QStringLiteral(u"template.qml") + << QStringLiteral(u"templateReformatted.qml") << defaultOptions; + + QTest::newRow("arrowFunctions") + << QStringLiteral(u"arrowFunctions.qml") + << QStringLiteral(u"arrowFunctionsReformatted.qml") << defaultOptions; + + QTest::newRow("file1NoReorder") + << QStringLiteral(u"file1.qml") << QStringLiteral(u"file1Reformatted2.qml") + << noReorderOptions; + QTest::newRow("noMerge") + << QStringLiteral(u"noMerge.qml") << QStringLiteral(u"noMergeReformatted.qml") + << defaultOptions; + } + + void manualReformatter() + { + QFETCH(QString, inFile); + QFETCH(QString, outFile); + QFETCH(LineWriterOptions, options); + + QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter"); + QStringList qmltypeDirs = + QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }); + auto envPtr = DomEnvironment::create( + qmltypeDirs, + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + QString testFilePath = baseDir + QLatin1Char('/') + inFile; + DomItem tFile; + envPtr->loadBuiltins(); + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, testFilePath), + [&tFile](Path, const DomItem &, const DomItem &newIt) { tFile = newIt; }); + envPtr->loadPendingDependencies(); + + QString resultStr; + QTextStream res(&resultStr); + LineWriter lw([&res](QStringView s) { res << s; }, QLatin1String("*testStream*"), options); + OutWriter ow(lw); + ow.indentNextlines = true; + DomItem qmlFile = tFile.field(Fields::currentItem); + qmlFile.writeOut(ow); + lw.eof(); + res.flush(); + QString fullRes = resultStr; + res.seek(0); + QFile fOut(baseDir + QLatin1Char('/') + outFile); + if (!fOut.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "could not open file" << outFile; + return; + } + QTextStream out(&fOut); + QString line = out.readLine(); + QString resLine = res.readLine(); + auto writeReformatted = [fullRes]() { + qDebug().noquote().nospace() << "Reformatted output:\n" + << "-----------------\n" + << fullRes << "-----------------\n"; + }; + while (!line.isNull() && !resLine.isNull()) { + if (resLine != line) + writeReformatted(); + QCOMPARE(resLine, line); + line = out.readLine(); + resLine = res.readLine(); + } + if (resLine.isNull() != line.isNull()) { + writeReformatted(); + qDebug() << "reformatted at end" << resLine.isNull() << resLine + << "reference at end:" << line.isNull() << line; + } + QCOMPARE(resLine.isNull(), line.isNull()); + } + + void indentInfo() + { + IndentInfo i1(u"\n\n ", 4); + QCOMPARE(i1.trailingString, u" "); + QCOMPARE(i1.nNewlines, 2); + QCOMPARE(i1.column, 2); + IndentInfo i2(u"\r\n\r\n ", 4); + QCOMPARE(i2.trailingString, u" "); + QCOMPARE(i2.nNewlines, 2); + QCOMPARE(i2.column, 2); + IndentInfo i3(u"\n ", 4); + QCOMPARE(i3.trailingString, u" "); + QCOMPARE(i3.nNewlines, 1); + QCOMPARE(i3.column, 1); + IndentInfo i4(u"\r\n ", 4); + QCOMPARE(i4.trailingString, u" "); + QCOMPARE(i4.nNewlines, 1); + QCOMPARE(i4.column, 1); + IndentInfo i5(u"\n", 4); + QCOMPARE(i5.trailingString, u""); + QCOMPARE(i5.nNewlines, 1); + QCOMPARE(i5.column, 0); + IndentInfo i6(u"\r\n", 4); + QCOMPARE(i6.trailingString, u""); + QCOMPARE(i6.nNewlines, 1); + QCOMPARE(i6.column, 0); + IndentInfo i7(u" ", 4); + QCOMPARE(i7.trailingString, u" "); + QCOMPARE(i7.nNewlines, 0); + QCOMPARE(i7.column, 2); + IndentInfo i8(u"", 4); + QCOMPARE(i8.trailingString, u""); + QCOMPARE(i8.nNewlines, 0); + QCOMPARE(i8.column, 0); + } + + void lineWriter() + { + { + QString res; + LineWriterOptions opts; + opts.lineEndings = LineWriterOptions::LineEndings::Unix; + LineWriter lw([&res](QStringView v) { res.append(v); }, QLatin1String("*testStream*"), + opts); + lw.write(u"a\nb"); + lw.write(u"c\r\nd"); + lw.write(u"e\rf"); + lw.write(u"g\r\n"); + lw.write(u"h\r"); + lw.write(u"\n"); + QCOMPARE(res, u"a\nbc\nde\nfg\nh\n\n"); + } + { + QString res; + LineWriterOptions opts; + opts.lineEndings = LineWriterOptions::LineEndings::Windows; + LineWriter lw([&res](QStringView v) { res.append(v); }, QLatin1String("*testStream*"), + opts); + lw.write(u"a\nb"); + lw.write(u"c\r\nd"); + lw.write(u"e\rf"); + lw.write(u"g\r\n"); + lw.write(u"h\r"); + lw.write(u"\n"); + QCOMPARE(res, u"a\r\nbc\r\nde\r\nfg\r\nh\r\n\r\n"); + } + { + QString res; + LineWriterOptions opts; + opts.lineEndings = LineWriterOptions::LineEndings::OldMacOs; + LineWriter lw([&res](QStringView v) { res.append(v); }, QLatin1String("*testStream*"), + opts); + lw.write(u"a\nb"); + lw.write(u"c\r\nd"); + lw.write(u"e\rf"); + lw.write(u"g\r\n"); + lw.write(u"h\r"); + lw.write(u"\n"); + QCOMPARE(res, u"a\rbc\rde\rfg\rh\r\r"); + } + } + + 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) {}"); + } + + // 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: +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif // TST_QMLDOMSCANNER_H diff --git a/tests/auto/qmldom/stringdumper/CMakeLists.txt b/tests/auto/qmldom/stringdumper/CMakeLists.txt index 1c809c2827..2b09281302 100644 --- a/tests/auto/qmldom/stringdumper/CMakeLists.txt +++ b/tests/auto/qmldom/stringdumper/CMakeLists.txt @@ -1,17 +1,25 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from stringdumper.pro. ##################################################################### ## 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.cpp tst_qmldomstringdumper.h DEFINES QT_DEPRECATED_WARNINGS INCLUDE_DIRECTORIES ../../../../src/qmldom - PUBLIC_LIBRARIES - Qt::QmlDevToolsPrivate + LIBRARIES Qt::QmlDomPrivate ) diff --git a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp index 04b9899d0f..fab3bc5be8 100644 --- a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp +++ b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.cpp @@ -1,115 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -**/ -#include <QtQmlDom/private/qqmldomstringdumper_p.h> - -#include <QtTest/QtTest> -#include <QTextStream> -#include <QDebug> - -#include <limits> - -QT_BEGIN_NAMESPACE -namespace QQmlJS { -namespace Dom { - -class TestStringDumper: public QObject -{ - Q_OBJECT -private slots: - void testDumperToString() { - QCOMPARE(dumperToString(u"bla"), QStringLiteral(u"bla")); - QCOMPARE(dumperToString([](Sink s) { s(u"bla"); s(u"bla"); }), QStringLiteral(u"blabla")); - } - - void testSinkInt() { - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, 1); }), QStringLiteral(u"1")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, 0); }), QStringLiteral(u"0")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, -1); }), QStringLiteral(u"-1")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint32>::max()); }), QStringLiteral(u"2147483647")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint32>::min()); }), QStringLiteral(u"-2147483648")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<quint32>::max()); }), QStringLiteral(u"4294967295")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint64>::min()); }), QStringLiteral(u"-9223372036854775808")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint64>::max()); }), QStringLiteral(u"9223372036854775807")); - QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<quint64>::max()); }), QStringLiteral(u"18446744073709551615")); - } - - void testSinkEscaped() { - QCOMPARE(dumperToString([](Sink s) { sinkEscaped(s, u""); }), QStringLiteral(u"\"\"")); - QStringView s1 = u"bla:\"bla\\\""; // uR"(bla:"bla\")"; - QStringView s1Escaped = u"\"bla:\\\"bla\\\\\\\"\""; - QCOMPARE(dumperToString([s1](Sink s) { sinkEscaped(s, s1); }), s1Escaped); - QCOMPARE(dumperToString([](Sink s) { sinkEscaped(s, u"", EscapeOptions::NoOuterQuotes); }), QStringLiteral(u"")); - QStringView s2 = u"bla:\"bla\\\"\n"; - QStringView s2Escaped = u"bla:\\\"bla\\\\\\\"\\n"; // uR"(bla:\"bla\\\"\n)" - QCOMPARE(dumperToString([s2](Sink s) { sinkEscaped(s, s2, EscapeOptions::NoOuterQuotes); }), s2Escaped); - } - - void testDevNull() { - sinkEscaped(devNull, u""); - sinkEscaped(devNull, u"bla:\"bla\\\"\n"); - } - - void testSinkIndent() { - QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 0); }), QStringLiteral(u"")); - QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 1); }), QStringLiteral(u" ")); - QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 10); }), QStringLiteral(u" ")); - QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 40); }), QStringLiteral(u" ").repeated(4)); - } - - - void testSinkNewline(){ - QCOMPARE(dumperToString([](Sink s) { sinkNewline(s); }), QStringLiteral(u"\n")); - QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 0); }), QStringLiteral(u"\n")); - QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 1); }), QStringLiteral(u"\n ")); - QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 10); }), QStringLiteral(u"\n ")); - QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 40); }), QStringLiteral(u"\n") + QStringLiteral(u" ").repeated(4)); - } - - void testDumpErrorLevel(){ - QCOMPARE(dumperToString([](Sink s) { dumpErrorLevel(s, ErrorLevel::Hint); }), QStringLiteral(u"Hint")); - QCOMPARE(dumperToString([](Sink s) { dumpErrorLevel(s, ErrorLevel::Error); }), QStringLiteral(u"Error")); - QCOMPARE(dumperToString([](Sink s) { dumpErrorLevel(s, ErrorLevel::Warning); }), QStringLiteral(u"Warning")); - } -}; - -} // namespace Dom -} // namespace QQmlJS -QT_END_NAMESPACE +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "tst_qmldomstringdumper.h" QTEST_MAIN(QQmlJS::Dom::TestStringDumper) -#include "tst_qmldomstringdumper.moc" diff --git a/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h new file mode 100644 index 0000000000..28e844db73 --- /dev/null +++ b/tests/auto/qmldom/stringdumper/tst_qmldomstringdumper.h @@ -0,0 +1,81 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QMLDOMSTRINGDUMPER_H +#define TST_QMLDOMSTRINGDUMPER_H +#include <QtQmlDom/private/qqmldomstringdumper_p.h> + +#include <QtTest/QtTest> +#include <QTextStream> +#include <QDebug> + +#include <limits> + +QT_BEGIN_NAMESPACE +namespace QQmlJS { +namespace Dom { + +class TestStringDumper: public QObject +{ + Q_OBJECT +private slots: + void testDumperToString() { + QCOMPARE(dumperToString(u"bla"), QStringLiteral(u"bla")); + QCOMPARE(dumperToString([](Sink s) { s(u"bla"); s(u"bla"); }), QStringLiteral(u"blabla")); + } + + void testSinkInt() { + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, 1); }), QStringLiteral(u"1")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, 0); }), QStringLiteral(u"0")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, -1); }), QStringLiteral(u"-1")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint32>::max()); }), QStringLiteral(u"2147483647")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint32>::min()); }), QStringLiteral(u"-2147483648")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<quint32>::max()); }), QStringLiteral(u"4294967295")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint64>::min()); }), QStringLiteral(u"-9223372036854775808")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<qint64>::max()); }), QStringLiteral(u"9223372036854775807")); + QCOMPARE(dumperToString([](Sink s) { sinkInt(s, std::numeric_limits<quint64>::max()); }), QStringLiteral(u"18446744073709551615")); + } + + void testSinkEscaped() { + QCOMPARE(dumperToString([](Sink s) { sinkEscaped(s, u""); }), QStringLiteral(u"\"\"")); + QStringView s1 = u"bla:\"bla\\\""; // uR"(bla:"bla\")"; + QStringView s1Escaped = u"\"bla:\\\"bla\\\\\\\"\""; + QCOMPARE(dumperToString([s1](Sink s) { sinkEscaped(s, s1); }), s1Escaped); + QCOMPARE(dumperToString([](Sink s) { sinkEscaped(s, u"", EscapeOptions::NoOuterQuotes); }), QStringLiteral(u"")); + QStringView s2 = u"bla:\"bla\\\"\n"; + QStringView s2Escaped = u"bla:\\\"bla\\\\\\\"\\n"; // uR"(bla:\"bla\\\"\n)" + QCOMPARE(dumperToString([s2](Sink s) { sinkEscaped(s, s2, EscapeOptions::NoOuterQuotes); }), s2Escaped); + } + + void testDevNull() { + sinkEscaped(devNull, u""); + sinkEscaped(devNull, u"bla:\"bla\\\"\n"); + } + + void testSinkIndent() { + QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 0); }), QStringLiteral(u"")); + QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 1); }), QStringLiteral(u" ")); + QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 10); }), QStringLiteral(u" ")); + QCOMPARE(dumperToString([](Sink s) { sinkIndent(s, 40); }), QStringLiteral(u" ").repeated(4)); + } + + + void testSinkNewline(){ + QCOMPARE(dumperToString([](Sink s) { sinkNewline(s); }), QStringLiteral(u"\n")); + QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 0); }), QStringLiteral(u"\n")); + QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 1); }), QStringLiteral(u"\n ")); + QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 10); }), QStringLiteral(u"\n ")); + QCOMPARE(dumperToString([](Sink s) { sinkNewline(s, 40); }), QStringLiteral(u"\n") + QStringLiteral(u" ").repeated(4)); + } + + void testDumpErrorLevel(){ + QCOMPARE(dumperToString([](Sink s) { dumpErrorLevel(s, ErrorLevel::Error); }), QStringLiteral(u"Error")); + QCOMPARE(dumperToString([](Sink s) { dumpErrorLevel(s, ErrorLevel::Warning); }), QStringLiteral(u"Warning")); + } +}; + +} // namespace Dom +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif |