aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/CMakeLists.txt125
-rw-r--r--tools/qml/CMakeLists.txt41
-rw-r--r--tools/qml/ResizeItemToWindow.qml (renamed from tools/qml/conf/content/resizeItemToWindow.qml)9
-rw-r--r--tools/qml/ResizeWindowToItem.qml (renamed from tools/qml/conf/content/resizeWindowToItem.qml)2
-rw-r--r--tools/qml/conf/default.qml10
-rw-r--r--tools/qml/conf/resizeToItem.qml10
-rw-r--r--tools/qml/default.qml10
-rw-r--r--tools/qml/main.cpp99
-rw-r--r--tools/qml/resizeToItem.qml10
-rw-r--r--tools/qmlaotstats/CMakeLists.txt17
-rw-r--r--tools/qmlaotstats/main.cpp83
-rw-r--r--tools/qmlcachegen/qmlcachegen.cpp127
-rw-r--r--tools/qmldom/qmldomtool.cpp16
-rw-r--r--tools/qmlformat/CMakeLists.txt3
-rw-r--r--tools/qmlformat/qmlformat.cpp145
-rw-r--r--tools/qmljs/CMakeLists.txt5
-rw-r--r--tools/qmljs/qmljs.cpp81
-rw-r--r--tools/qmljsrootgen/main.cpp27
-rw-r--r--tools/qmllint/CMakeLists.txt3
-rw-r--r--tools/qmllint/main.cpp250
-rw-r--r--tools/qmlls/CMakeLists.txt26
-rw-r--r--tools/qmlls/lspcustomtypes.h44
-rw-r--r--tools/qmlls/qlanguageserver.cpp376
-rw-r--r--tools/qmlls/qlanguageserver.h91
-rw-r--r--tools/qmlls/qlanguageserver_p.h52
-rw-r--r--tools/qmlls/qmlcompletionsupport.cpp665
-rw-r--r--tools/qmlls/qmlcompletionsupport.h47
-rw-r--r--tools/qmlls/qmllanguageservertool.cpp224
-rw-r--r--tools/qmlls/qmllintsuggestions.cpp288
-rw-r--r--tools/qmlls/qmllintsuggestions.h40
-rw-r--r--tools/qmlls/qqmlcodemodel.cpp700
-rw-r--r--tools/qmlls/qqmlcodemodel.h128
-rw-r--r--tools/qmlls/qqmllanguageserver.cpp146
-rw-r--r--tools/qmlls/qqmllanguageserver.h66
-rw-r--r--tools/qmlls/textblock.cpp101
-rw-r--r--tools/qmlls/textblock.h61
-rw-r--r--tools/qmlls/textcursor.cpp121
-rw-r--r--tools/qmlls/textcursor.h60
-rw-r--r--tools/qmlls/textdocument.cpp116
-rw-r--r--tools/qmlls/textdocument.h66
-rw-r--r--tools/qmlls/textsynchronization.cpp99
-rw-r--r--tools/qmlls/textsynchronization.h32
-rw-r--r--tools/qmlls/workspace.cpp164
-rw-r--r--tools/qmlls/workspace.h32
-rw-r--r--tools/qmlplugindump/main.cpp220
-rw-r--r--tools/qmlprofiler/qmlprofilerdata.cpp210
-rw-r--r--tools/qmltc/CMakeLists.txt2
-rw-r--r--tools/qmltc/main.cpp55
-rw-r--r--tools/qmltc/qmltccodewriter.cpp99
-rw-r--r--tools/qmltc/qmltccodewriter.h4
-rw-r--r--tools/qmltc/qmltccompiler.cpp397
-rw-r--r--tools/qmltc/qmltccompiler.h8
-rw-r--r--tools/qmltc/qmltccompilerpieces.cpp17
-rw-r--r--tools/qmltc/qmltccompilerpieces.h27
-rw-r--r--tools/qmltc/qmltcoutputir.h46
-rw-r--r--tools/qmltc/qmltcpropertyutils.h11
-rw-r--r--tools/qmltc/qmltctyperesolver.cpp5
-rw-r--r--tools/qmltc/qmltcvisitor.cpp79
-rw-r--r--tools/qmltc/qmltcvisitor.h8
-rw-r--r--tools/qmltime/qmltime.h1
-rw-r--r--tools/qmltyperegistrar/qmltyperegistrar.cpp81
-rw-r--r--tools/shared/qqmltoolingsettings.cpp125
-rw-r--r--tools/shared/qqmltoolingsettings.h33
-rw-r--r--tools/svgtoqml/CMakeLists.txt32
-rw-r--r--tools/svgtoqml/main.cpp141
-rw-r--r--tools/svgtoqml/main.qml21
66 files changed, 1996 insertions, 4444 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index a99cd33c24..0b89bea46e 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -3,79 +3,70 @@
# Generated from tools.pro.
-if(QT_FEATURE_qml_devtools)
- add_subdirectory(qmldom)
- if(QT_FEATURE_commandlineparser)
- add_subdirectory(qmllint)
- add_subdirectory(qmltc)
- add_subdirectory(qmltyperegistrar)
- endif()
- add_subdirectory(qmlimportscanner)
- add_subdirectory(qmlformat)
- if (TARGET Qt::LanguageServerPrivate
- AND NOT WASM AND NOT IOS AND NOT ANDROID AND NOT QNX AND NOT INTEGRITY AND NOT WEBOS)
+add_subdirectory(qmldom)
+if(QT_FEATURE_commandlineparser)
+ add_subdirectory(qmllint)
+ add_subdirectory(qmltc)
+ add_subdirectory(qmltyperegistrar)
+ add_subdirectory(qmljsrootgen)
+endif()
+add_subdirectory(qmlimportscanner)
+add_subdirectory(qmlformat)
+if(TARGET Qt::LanguageServerPrivate AND QT_FEATURE_commandlineparser AND QT_FEATURE_filesystemwatcher)
+ if (NOT CMAKE_CROSSCOMPILING OR QT_FORCE_BUILD_TOOLS)
add_subdirectory(qmlls)
endif()
endif()
-if(QT_FEATURE_qml_devtools AND QT_FEATURE_xmlstreamwriter)
+if(QT_FEATURE_xmlstreamwriter)
# special case begin
# Do not build qmlcachegen here but build it at src/
# time, so that we can use it for our own .qml files in src/imports.
# add_subdirectory(qmlcachegen)
# special case end
endif()
-if(QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT IOS AND NOT rtems)
- add_subdirectory(qml)
-endif()
-if(QT_FEATURE_qml_profiler
- AND QT_FEATURE_thread
- AND NOT ANDROID
- AND NOT IOS
- AND NOT WASM
- AND NOT rtems)
- add_subdirectory(qmlprofiler)
-endif()
-if(QT_FEATURE_qml_preview AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT IOS AND NOT rtems)
- add_subdirectory(qmlpreview)
-endif()
-if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_thread AND TARGET Qt::Quick AND NOT ANDROID AND NOT WASM AND NOT IOS AND NOT rtems)
- add_subdirectory(qmlscene)
- add_subdirectory(qmltime)
-endif()
-if(QT_BUILD_SHARED_LIBS
- AND QT_FEATURE_process
- AND QT_FEATURE_regularexpression
- AND QT_FEATURE_thread
- AND TARGET Qt::Quick
- AND NOT ANDROID
- AND NOT IOS
- AND NOT WASM
- AND NOT rtems)
- add_subdirectory(qmlplugindump)
-endif()
-if(TARGET Qt::Quick
- AND TARGET Qt::Widgets
- AND QT_FEATURE_checkbox
- AND QT_FEATURE_combobox
- AND QT_FEATURE_dialogbuttonbox
- AND QT_FEATURE_formlayout
- AND QT_FEATURE_groupbox
- AND QT_FEATURE_lineedit
- AND QT_FEATURE_mainwindow
- AND QT_FEATURE_spinbox
- AND QT_FEATURE_textedit
- AND NOT ANDROID
- AND NOT IOS
- AND NOT WASM
- AND NOT rtems)
- add_subdirectory(qmleasing)
-endif()
-if(QT_FEATURE_thread AND TARGET Qt::QuickTest AND NOT ANDROID AND NOT WASM AND NOT rtems)
- add_subdirectory(qmltestrunner)
-endif()
-if(QT_FEATURE_private_tests AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT IOS AND NOT rtems)
- add_subdirectory(qmljs)
-endif()
-if (QT_FEATURE_private_tests AND NOT CMAKE_CROSSCOMPILING)
- add_subdirectory(qmljsrootgen)
-endif()
+
+if(NOT (ANDROID OR WASM OR IOS OR VISIONOS OR rtems))
+ if(QT_FEATURE_thread)
+ add_subdirectory(qml)
+ if(QT_FEATURE_qml_profiler)
+ add_subdirectory(qmlprofiler)
+ endif()
+ if(QT_FEATURE_qml_preview)
+ add_subdirectory(qmlpreview)
+ endif()
+ if(QT_BUILD_SHARED_LIBS AND TARGET Qt::Quick)
+ add_subdirectory(qmlscene)
+ add_subdirectory(qmltime)
+ endif()
+ if(QT_BUILD_SHARED_LIBS
+ AND QT_FEATURE_process
+ AND QT_FEATURE_regularexpression
+ AND TARGET Qt::Quick)
+ add_subdirectory(qmlplugindump)
+ endif()
+ if(TARGET Qt::QuickTest)
+ add_subdirectory(qmltestrunner)
+ endif()
+ if(QT_FEATURE_private_tests)
+ add_subdirectory(qmljs)
+ endif()
+ endif() # QT_FEATURE_thread
+
+ if(TARGET Qt::Quick
+ AND TARGET Qt::Widgets
+ AND QT_FEATURE_checkbox
+ AND QT_FEATURE_combobox
+ AND QT_FEATURE_dialogbuttonbox
+ AND QT_FEATURE_formlayout
+ AND QT_FEATURE_groupbox
+ AND QT_FEATURE_lineedit
+ AND QT_FEATURE_mainwindow
+ AND QT_FEATURE_spinbox
+ AND QT_FEATURE_textedit)
+ add_subdirectory(qmleasing)
+ endif()
+
+ if(TARGET Qt::Quick AND TARGET Qt::Svg)
+ add_subdirectory(svgtoqml)
+ endif()
+endif() # NOT (ANDROID OR WASM OR IOS OR rtems)
diff --git a/tools/qml/CMakeLists.txt b/tools/qml/CMakeLists.txt
index 0bbde6d288..b209c730d3 100644
--- a/tools/qml/CMakeLists.txt
+++ b/tools/qml/CMakeLists.txt
@@ -19,35 +19,18 @@ qt_internal_add_app(qml
set_target_properties(qml PROPERTIES WIN32_EXECUTABLE FALSE)
-set_source_files_properties(
- conf/default.qml
- conf/resizeToItem.qml
- conf/content/resizeItemToWindow.qml
- conf/content/resizeWindowToItem.qml
- PROPERTIES QT_SKIP_QUICKCOMPILER 1
-)
-
-# Resources:
-set(qml_resource_files
- "conf/content/resizeItemToWindow.qml"
- "conf/content/resizeWindowToItem.qml"
- "conf/default.qml"
- "conf/resizeToItem.qml"
- "resources/qml-64.png"
-)
-
-qt_internal_add_resource(qml qml
- PREFIX
- "/qt-project.org/QmlRuntime"
- FILES
- ${qml_resource_files}
-)
-
# Turn the tool into its own self-contained qml module
qt6_add_qml_module(qml
- RESOURCE_PREFIX "/qt-project.org"
+ RESOURCE_PREFIX "/qt-project.org/imports"
URI QmlRuntime.Config
VERSION 1.0
+ QML_FILES
+ default.qml
+ resizeToItem.qml
+ ResizeItemToWindow.qml
+ ResizeWindowToItem.qml
+ RESOURCES
+ resources/qml-64.png
)
qt_internal_extend_target(qml CONDITION TARGET Qt::Gui
@@ -60,14 +43,6 @@ qt_internal_extend_target(qml CONDITION TARGET Qt::Widgets
Qt::Widgets
)
-#### Keys ignored in scope 4:.:.:qml.pro:WIN32:
-# RC_ICONS = "resources/qml.ico"
-
-#### Keys ignored in scope 5:.:.:qml.pro:APPLE:
-# ICON = "resources/qml.icns"
-# OTHER_FILES = "resources/Info.plist"
-# QMAKE_INFO_PLIST = "resources/Info.plist"
-
qt_internal_extend_target(qml CONDITION QT_FEATURE_qml_debug
DEFINES
QT_QML_DEBUG_NO_WARNING
diff --git a/tools/qml/conf/content/resizeItemToWindow.qml b/tools/qml/ResizeItemToWindow.qml
index 4a6d06591c..a4d8bfec40 100644
--- a/tools/qml/conf/content/resizeItemToWindow.qml
+++ b/tools/qml/ResizeItemToWindow.qml
@@ -1,23 +1,20 @@
// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick.Window 2.0
import QtQuick 2.0
Window {
property Item containedObject: null
- property bool __resizeGuard: false
onContainedObjectChanged: {
if (containedObject == undefined || containedObject == null) {
visible = false;
return;
}
- __resizeGuard = true
width = containedObject.width;
height = containedObject.height;
containedObject.parent = contentItem;
visible = true;
- __resizeGuard = false
}
- onWidthChanged: if (!__resizeGuard && containedObject) containedObject.width = width
- onHeightChanged: if (!__resizeGuard && containedObject) containedObject.height = height
+ onWidthChanged: if (containedObject) containedObject.width = width
+ onHeightChanged: if (containedObject) containedObject.height = height
}
diff --git a/tools/qml/conf/content/resizeWindowToItem.qml b/tools/qml/ResizeWindowToItem.qml
index 942e739d13..b969971bc2 100644
--- a/tools/qml/conf/content/resizeWindowToItem.qml
+++ b/tools/qml/ResizeWindowToItem.qml
@@ -1,5 +1,5 @@
// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick.Window 2.0
import QtQuick 2.0
diff --git a/tools/qml/conf/default.qml b/tools/qml/conf/default.qml
deleted file mode 100644
index c9bae6dcd0..0000000000
--- a/tools/qml/conf/default.qml
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QmlRuntime.Config 1.0
-
-Configuration {
- PartialScene {
- itemType: "QQuickItem"
- container: "qrc:/qt-project.org/QmlRuntime/conf/content/resizeItemToWindow.qml"
- }
-}
diff --git a/tools/qml/conf/resizeToItem.qml b/tools/qml/conf/resizeToItem.qml
deleted file mode 100644
index 7c1389a7a7..0000000000
--- a/tools/qml/conf/resizeToItem.qml
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (C) 2019 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-import QmlRuntime.Config 1.0
-
-Configuration {
- PartialScene {
- itemType: "QQuickItem"
- container: "qrc:/qt-project.org/QmlRuntime/conf/content/resizeWindowToItem.qml"
- }
-}
diff --git a/tools/qml/default.qml b/tools/qml/default.qml
new file mode 100644
index 0000000000..54a521193c
--- /dev/null
+++ b/tools/qml/default.qml
@@ -0,0 +1,10 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+import QmlRuntime.Config
+
+Configuration {
+ PartialScene {
+ itemType: "QQuickItem"
+ container: "qrc:/qt-project.org/imports/QmlRuntime/Config/ResizeItemToWindow.qml"
+ }
+}
diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp
index ae5596e000..da544c5563 100644
--- a/tools/qml/main.cpp
+++ b/tools/qml/main.cpp
@@ -73,12 +73,18 @@ static QQmlApplicationEngine *qae = nullptr;
#if defined(Q_OS_DARWIN) || defined(QT_GUI_LIB)
static int exitTimerId = -1;
#endif
-static const QString iconResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png"));
-static const QString confResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/conf/"));
+static const QString iconResourcePath(QStringLiteral(":/qt-project.org/imports/QmlRuntime/Config/resources/qml-64.png"));
+static const QString confResourcePath(QStringLiteral(":/qt-project.org/imports/QmlRuntime/Config/"));
static const QString customConfFileName(QStringLiteral("configuration.qml"));
static bool verboseMode = false;
static bool quietMode = false;
static bool glShareContexts = true;
+static bool disableShaderCache = true;
+#if defined(QT_GUI_LIB)
+static bool requestAlphaChannel = false;
+static bool requestMSAA = false;
+static bool requestCoreProfile = false;
+#endif
static void loadConf(const QString &override, bool quiet) // Terminates app on failure
{
@@ -152,8 +158,16 @@ static void listConfFiles()
{
const QDir confResourceDir(confResourcePath);
printf("%s\n", qPrintable(QCoreApplication::translate("main", "Built-in configurations:")));
- for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files))
- printf(" %s\n", qPrintable(fi.baseName()));
+ for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files)) {
+ if (fi.completeSuffix() != QLatin1String("qml"))
+ continue;
+
+ const QString baseName = fi.baseName();
+ if (baseName.isEmpty() || baseName[0].isUpper())
+ continue;
+
+ printf(" %s\n", qPrintable(baseName));
+ }
printf("%s\n", qPrintable(QCoreApplication::translate("main", "Other configurations:")));
bool foundOther = false;
const QStringList otherLocations = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation);
@@ -324,6 +338,14 @@ static void getAppFlags(int argc, char **argv)
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
} else if (!strcmp(argv[i], "-disable-context-sharing") || !strcmp(argv[i], "--disable-context-sharing")) {
glShareContexts = false;
+ } else if (!strcmp(argv[i], "-enable-shader-cache") || !strcmp(argv[i], "--enable-shader-cache")) {
+ disableShaderCache = false;
+ } else if (!strcmp(argv[i], "-transparent") || !strcmp(argv[i], "--transparent")) {
+ requestAlphaChannel = true;
+ } else if (!strcmp(argv[i], "-multisample") || !strcmp(argv[i], "--multisample")) {
+ requestMSAA = true;
+ } else if (!strcmp(argv[i], "-core-profile") || !strcmp(argv[i], "--core-profile")) {
+ requestCoreProfile = true;
}
}
#else
@@ -362,8 +384,31 @@ int main(int argc, char *argv[])
{
getAppFlags(argc, argv);
+ // Must set the default QSurfaceFormat before creating the app object if
+ // AA_ShareOpenGLContexts is going to be set.
+#if defined(QT_GUI_LIB)
+ QSurfaceFormat surfaceFormat;
+ surfaceFormat.setDepthBufferSize(24);
+ surfaceFormat.setStencilBufferSize(8);
+ if (requestMSAA)
+ surfaceFormat.setSamples(4);
+ if (requestAlphaChannel)
+ surfaceFormat.setAlphaBufferSize(8);
+ if (qEnvironmentVariableIsSet("QSG_CORE_PROFILE")
+ || qEnvironmentVariableIsSet("QML_CORE_PROFILE")
+ || requestCoreProfile)
+ {
+ // intentionally requesting 4.1 core to play nice with macOS
+ surfaceFormat.setVersion(4, 1);
+ surfaceFormat.setProfile(QSurfaceFormat::CoreProfile);
+ }
+ QSurfaceFormat::setDefaultFormat(surfaceFormat);
+#endif
+
if (glShareContexts)
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
+ if (disableShaderCache)
+ QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache);
std::unique_ptr<QCoreApplication> app;
switch (applicationType) {
@@ -390,7 +435,6 @@ int main(int argc, char *argv[])
app->setOrganizationDomain("qt-project.org");
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
- QQmlApplicationEngine e;
QStringList files;
QString confFile;
QString translationFile;
@@ -399,8 +443,8 @@ int main(int argc, char *argv[])
QCommandLineParser parser;
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments);
- const QCommandLineOption helpOption = parser.addHelpOption();
- const QCommandLineOption versionOption = parser.addVersionOption();
+ parser.addHelpOption();
+ parser.addVersionOption();
#ifdef QT_GUI_LIB
QCommandLineOption apptypeOption(QStringList() << QStringLiteral("a") << QStringLiteral("apptype"),
QCoreApplication::translate("main", "Select which application class to use. Default is gui."),
@@ -444,11 +488,22 @@ int main(int argc, char *argv[])
parser.addOption(glSoftwareOption); // Just for the help text... we've already handled this argument above
QCommandLineOption glCoreProfile(QStringLiteral("core-profile"),
QCoreApplication::translate("main", "Force use of OpenGL Core Profile."));
- parser.addOption(glCoreProfile);
+ parser.addOption(glCoreProfile); // Just for the help text... we've already handled this argument above
QCommandLineOption glContextSharing(QStringLiteral("disable-context-sharing"),
QCoreApplication::translate("main", "Disable the use of a shared GL context for QtQuick Windows"));
parser.addOption(glContextSharing); // Just for the help text... we've already handled this argument above
+ // Options relevant for other 3D APIs as well
+ QCommandLineOption shaderCaching(QStringLiteral("enable-shader-cache"),
+ QCoreApplication::translate("main", "Enable persistent caching of generated shaders"));
+ parser.addOption(shaderCaching); // Just for the help text... we've already handled this argument above
+ QCommandLineOption transparentOption(QStringLiteral("transparent"),
+ QCoreApplication::translate("main", "Requests an alpha channel in order to enable semi-transparent windows."));
+ parser.addOption(transparentOption); // Just for the help text... we've already handled this argument above
+ QCommandLineOption multisampleOption(QStringLiteral("multisample"),
+ QCoreApplication::translate("main", "Requests 4x multisample antialiasing."));
+ parser.addOption(multisampleOption); // Just for the help text... we've already handled this argument above
#endif // QT_GUI_LIB
+
// Debugging and verbosity options
QCommandLineOption quietOption(QStringLiteral("quiet"),
QCoreApplication::translate("main", "Suppress all output."));
@@ -464,7 +519,7 @@ int main(int argc, char *argv[])
parser.addOption(fixedAnimationsOption);
QCommandLineOption rhiOption(QStringList() << QStringLiteral("r") << QStringLiteral("rhi"),
QCoreApplication::translate("main", "Set the backend for the Qt graphics abstraction (RHI). "
- "Backend is one of: default, vulkan, metal, d3d11, gl"),
+ "Backend is one of: default, vulkan, metal, d3d11, d3d12, opengl"),
QStringLiteral("backend"));
parser.addOption(rhiOption);
QCommandLineOption selectorOption(QStringLiteral("S"), QCoreApplication::translate("main",
@@ -477,14 +532,7 @@ int main(int argc, char *argv[])
parser.addPositionalArgument("args",
QCoreApplication::translate("main", "Arguments after '--' are ignored, but passed through to the application.arguments variable in QML."), "[-- args...]");
- if (!parser.parse(QCoreApplication::arguments())) {
- qWarning() << parser.errorText();
- exit(1);
- }
- if (parser.isSet(versionOption))
- parser.showVersion();
- if (parser.isSet(helpOption))
- parser.showHelp();
+ parser.process(*app);
if (parser.isSet(verboseOption))
verboseMode = true;
if (parser.isSet(quietOption)) {
@@ -507,6 +555,9 @@ int main(int argc, char *argv[])
if (parser.isSet(fixedAnimationsOption))
QUnifiedTimer::instance()->setConsistentTiming(true);
#endif
+
+ QQmlApplicationEngine e;
+
for (const QString &importPath : parser.values(importOption))
e.addImportPath(importPath);
@@ -517,17 +568,6 @@ int main(int argc, char *argv[])
if (!customSelectors.isEmpty())
e.setExtraFileSelectors(customSelectors);
-#if defined(QT_GUI_LIB)
- if (qEnvironmentVariableIsSet("QSG_CORE_PROFILE") || qEnvironmentVariableIsSet("QML_CORE_PROFILE") || parser.isSet(glCoreProfile)) {
- QSurfaceFormat surfaceFormat;
- surfaceFormat.setStencilBufferSize(8);
- surfaceFormat.setDepthBufferSize(24);
- surfaceFormat.setVersion(4, 1);
- surfaceFormat.setProfile(QSurfaceFormat::CoreProfile);
- QSurfaceFormat::setDefaultFormat(surfaceFormat);
- }
-#endif
-
files << parser.values(qmlFileOption);
if (parser.isSet(configOption))
confFile = parser.value(configOption);
@@ -550,9 +590,8 @@ int main(int argc, char *argv[])
#if QT_CONFIG(translation)
// Need to be installed before QQmlApplicationEngine's automatic translation loading
// (qt_ translations are loaded there)
+ QTranslator translator;
if (!translationFile.isEmpty()) {
- QTranslator translator;
-
if (translator.load(translationFile)) {
app->installTranslator(&translator);
if (verboseMode)
diff --git a/tools/qml/resizeToItem.qml b/tools/qml/resizeToItem.qml
new file mode 100644
index 0000000000..5bddd8ebaa
--- /dev/null
+++ b/tools/qml/resizeToItem.qml
@@ -0,0 +1,10 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+import QmlRuntime.Config
+
+Configuration {
+ PartialScene {
+ itemType: "QQuickItem"
+ container: "qrc:/qt-project.org/imports/QmlRuntime/Config/ResizeWindowToItem.qml"
+ }
+}
diff --git a/tools/qmlaotstats/CMakeLists.txt b/tools/qmlaotstats/CMakeLists.txt
new file mode 100644
index 0000000000..1511f19e4b
--- /dev/null
+++ b/tools/qmlaotstats/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_get_tool_target_name(target_name qmlaotstats)
+qt_internal_add_tool(${target_name}
+ TARGET_DESCRIPTION "QML ahead-of-time compiler statistics aggregator"
+ TOOLS_TARGET Qml # special case
+ INSTALL_DIR "${INSTALL_LIBEXECDIR}"
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::QmlPrivate
+ Qt::QmlCompilerPrivate
+ Qt::QmlToolingSettingsPrivate
+)
+qt_internal_return_unless_building_tools()
diff --git a/tools/qmlaotstats/main.cpp b/tools/qmlaotstats/main.cpp
new file mode 100644
index 0000000000..24b34efec3
--- /dev/null
+++ b/tools/qmlaotstats/main.cpp
@@ -0,0 +1,83 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include <private/qqmljscompilerstats_p.h>
+#include <private/qqmljscompilerstatsreporter_p.h>
+
+using namespace Qt::Literals::StringLiterals;
+
+bool saveFormattedStats(const QString &stats, const QString &outputPath)
+{
+ QString directory = QFileInfo(outputPath).dir().path();
+ if (!QDir().mkpath(directory)) {
+ qDebug() << "Could not ensure the existence of" << directory;
+ return false;
+ }
+
+ QFile outputFile(outputPath);
+ if (!outputFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
+ qDebug() << "Could not open file" << outputPath;
+ return false;
+ }
+
+ if (outputFile.write(stats.toLatin1()) == -1) {
+ qDebug() << "Could not write formatted AOT stats to" << outputPath;
+ return false;
+ } else {
+ qDebug() << "Formatted AOT stats saved to" << outputPath;
+ }
+
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
+
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.setApplicationDescription("Internal development tool.");
+ parser.addPositionalArgument("mode", "Choose whether to aggregate or display aotstats files",
+ "[aggregate|format]");
+ parser.addPositionalArgument("input", "Aggregate mode: the aotstatslist file to aggregate. "
+ "Format mode: the aotstats file to display.");
+ parser.addPositionalArgument("output", "Aggregate mode: the path where to store the "
+ "aggregated aotstats. Format mode: the the path where "
+ "the formatted output will be saved.");
+ parser.process(app);
+
+ const auto &positionalArgs = parser.positionalArguments();
+ if (positionalArgs.size() != 3) {
+ qDebug().noquote() << parser.helpText();
+ return EXIT_FAILURE;
+ }
+
+ const auto &mode = positionalArgs.first();
+ if (mode == u"aggregate"_s) {
+ const auto aggregated = QQmlJS::AotStats::aggregateAotstatsList(positionalArgs[1]);
+ if (!aggregated.has_value())
+ return EXIT_FAILURE;
+ if (!aggregated->saveToDisk(positionalArgs[2]))
+ return EXIT_FAILURE;
+
+ } else if (mode == u"format"_s) {
+ const auto aotstats = QQmlJS::AotStats::parseAotstatsFile(positionalArgs[1]);
+ if (!aotstats.has_value())
+ return EXIT_FAILURE;
+ const QQmlJS::AotStatsReporter reporter(aotstats.value());
+ if (!saveFormattedStats(reporter.format(), positionalArgs[2]))
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp
index 93c534d429..37a2298717 100644
--- a/tools/qmlcachegen/qmlcachegen.cpp
+++ b/tools/qmlcachegen/qmlcachegen.cpp
@@ -15,21 +15,24 @@
#include <QLoggingCategory>
#include <private/qqmlirbuilder_p.h>
-#include <private/qqmljsparser_p.h>
+#include <private/qqmljscompiler_p.h>
#include <private/qqmljslexer_p.h>
-#include <private/qqmljsresourcefilemapper_p.h>
#include <private/qqmljsloadergenerator_p.h>
-#include <private/qqmljscompiler_p.h>
+#include <private/qqmljsparser_p.h>
+#include <private/qqmljsresourcefilemapper_p.h>
+#include <private/qqmljsutils_p.h>
#include <private/qresourcerelocater_p.h>
#include <algorithm>
+using namespace Qt::Literals::StringLiterals;
+
static bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments)
{
allArguments.reserve(arguments.size());
for (const QString &argument : arguments) {
// "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
- if (argument.startsWith(QLatin1Char('@'))) {
+ if (argument.startsWith(u'@')) {
QString optionsFile = argument;
optionsFile.remove(0, 1);
if (optionsFile.isEmpty()) {
@@ -59,51 +62,54 @@ int main(int argc, char **argv)
QHashSeed::setDeterministicGlobalSeed();
QCoreApplication app(argc, argv);
- QCoreApplication::setApplicationName(QStringLiteral("qmlcachegen"));
+ QCoreApplication::setApplicationName("qmlcachegen"_L1);
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
- QCommandLineOption bareOption(QStringLiteral("bare"), QCoreApplication::translate("main", "Do not include default import directories. This may be used to run qmlcachegen on a project using a different Qt version."));
+ QCommandLineOption bareOption("bare"_L1, QCoreApplication::translate("main", "Do not include default import directories. This may be used to run qmlcachegen on a project using a different Qt version."));
parser.addOption(bareOption);
- QCommandLineOption filterResourceFileOption(QStringLiteral("filter-resource-file"), QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead"));
+ QCommandLineOption filterResourceFileOption("filter-resource-file"_L1, QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead"));
parser.addOption(filterResourceFileOption);
- QCommandLineOption resourceFileMappingOption(QStringLiteral("resource-file-mapping"), QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name=new-name"));
+ QCommandLineOption resourceFileMappingOption("resource-file-mapping"_L1, QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name=new-name"));
parser.addOption(resourceFileMappingOption);
- QCommandLineOption resourceOption(QStringLiteral("resource"), QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name"));
+ QCommandLineOption resourceOption("resource"_L1, QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name"));
parser.addOption(resourceOption);
- QCommandLineOption resourcePathOption(QStringLiteral("resource-path"), QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path"));
+ QCommandLineOption resourcePathOption("resource-path"_L1, QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path"));
parser.addOption(resourcePathOption);
- QCommandLineOption resourceNameOption(QStringLiteral("resource-name"),
- QCoreApplication::translate("main", "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."),
- QCoreApplication::translate("main", "compiled-file-list"));
+ QCommandLineOption resourceNameOption("resource-name"_L1, QCoreApplication::translate("main", "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."), QCoreApplication::translate("main", "compiled-file-list"));
parser.addOption(resourceNameOption);
- QCommandLineOption directCallsOption(QStringLiteral("direct-calls"), QCoreApplication::translate("main", "This option is ignored."));
+ QCommandLineOption directCallsOption("direct-calls"_L1, QCoreApplication::translate("main", "This option is ignored."));
directCallsOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(directCallsOption);
- QCommandLineOption importsOption(
- QStringLiteral("i"),
- QCoreApplication::translate("main", "Import extra qmldir"),
- QCoreApplication::translate("main", "qmldir file"));
+ QCommandLineOption staticOption("static"_L1, QCoreApplication::translate("main", "This option is ignored."));
+ staticOption.setFlags(QCommandLineOption::HiddenFromHelp);
+ parser.addOption(staticOption);
+ QCommandLineOption importsOption("i"_L1, QCoreApplication::translate("main", "Import extra qmldir"), QCoreApplication::translate("main", "qmldir file"));
parser.addOption(importsOption);
- QCommandLineOption importPathOption(
- QStringLiteral("I"),
- QCoreApplication::translate("main", "Look for QML modules in specified directory"),
- QCoreApplication::translate("main", "import directory"));
+ QCommandLineOption importPathOption("I"_L1, QCoreApplication::translate("main", "Look for QML modules in specified directory"), QCoreApplication::translate("main", "import directory"));
parser.addOption(importPathOption);
- QCommandLineOption onlyBytecode(
- QStringLiteral("only-bytecode"),
- QCoreApplication::translate(
- "main", "Generate only byte code for bindings and functions, no C++ code"));
+ QCommandLineOption onlyBytecode("only-bytecode"_L1, QCoreApplication::translate("main", "Generate only byte code for bindings and functions, no C++ code"));
parser.addOption(onlyBytecode);
+ QCommandLineOption verboseOption("verbose"_L1, QCoreApplication::translate("main", "Output compile warnings"));
+ parser.addOption(verboseOption);
+ QCommandLineOption warningsAreErrorsOption("warnings-are-errors"_L1, QCoreApplication::translate("main", "Treat warnings as errors"));
+ parser.addOption(warningsAreErrorsOption);
+
+ QCommandLineOption validateBasicBlocksOption("validate-basic-blocks"_L1, QCoreApplication::translate("main", "Performs checks on the basic blocks of a function compiled ahead of time to validate its structure and coherence"));
+ parser.addOption(validateBasicBlocksOption);
+
+ QCommandLineOption dumpAotStatsOption("dump-aot-stats"_L1, QCoreApplication::translate("main", "Dumps statistics about ahead-of-time compilation of bindings and functions"));
+ parser.addOption(dumpAotStatsOption);
+ QCommandLineOption moduleIdOption("module-id"_L1, QCoreApplication::translate("main", "Identifies the module of the qml file being compiled for aot stats"), QCoreApplication::translate("main", "id"));
+ parser.addOption(moduleIdOption);
- QCommandLineOption outputFileOption(QStringLiteral("o"), QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name"));
+ QCommandLineOption outputFileOption("o"_L1, QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name"));
parser.addOption(outputFileOption);
- parser.addPositionalArgument(QStringLiteral("[qml file]"),
- QStringLiteral("QML source file to generate cache for."));
+ parser.addPositionalArgument("[qml file]"_L1, "QML source file to generate cache for."_L1);
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
@@ -125,26 +131,31 @@ int main(int argc, char **argv)
if (parser.isSet(outputFileOption))
outputFileName = parser.value(outputFileOption);
- if (outputFileName.endsWith(QLatin1String(".cpp"))) {
+ if (outputFileName.endsWith(".cpp"_L1)) {
target = GenerateCpp;
- if (outputFileName.endsWith(QLatin1String("qmlcache_loader.cpp")))
+ if (outputFileName.endsWith("qmlcache_loader.cpp"_L1))
target = GenerateLoader;
}
if (target == GenerateLoader && parser.isSet(resourceNameOption))
target = GenerateLoaderStandAlone;
+ if (parser.isSet(dumpAotStatsOption) && !parser.isSet(moduleIdOption)) {
+ fprintf(stderr, "--dump-aot-stats set without setting --module-id");
+ return EXIT_FAILURE;
+ }
+
const QStringList sources = parser.positionalArguments();
if (sources.isEmpty()){
parser.showHelp();
} else if (sources.size() > 1 && (target != GenerateLoader && target != GenerateLoaderStandAlone)) {
- fprintf(stderr, "%s\n", qPrintable(QStringLiteral("Too many input files specified: '") + sources.join(QStringLiteral("' '")) + QLatin1Char('\'')));
+ fprintf(stderr, "%s\n", qPrintable("Too many input files specified: '"_L1 + sources.join("' '"_L1) + u'\''));
return EXIT_FAILURE;
}
const QString inputFile = !sources.isEmpty() ? sources.first() : QString();
if (outputFileName.isEmpty())
- outputFileName = inputFile + QLatin1Char('c');
+ outputFileName = inputFile + u'c';
if (parser.isSet(filterResourceFileOption))
return qRelocateResourceFile(inputFile, outputFileName);
@@ -156,7 +167,7 @@ int main(int argc, char **argv)
if (!qQmlJSGenerateLoader(
mapper.resourcePaths(QQmlJSResourceFileMapper::allQmlJSFilter()),
outputFileName, parser.values(resourceFileMappingOption), &error.message)) {
- error.augment(QLatin1String("Error generating loader stub: ")).print();
+ error.augment("Error generating loader stub: "_L1).print();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
@@ -166,7 +177,7 @@ int main(int argc, char **argv)
QQmlJSCompileError error;
if (!qQmlJSGenerateLoader(sources, outputFileName,
parser.values(resourceNameOption), &error.message)) {
- error.augment(QLatin1String("Error generating loader stub: ")).print();
+ error.augment("Error generating loader stub: "_L1).print();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
@@ -201,13 +212,12 @@ int main(int argc, char **argv)
}
if (target == GenerateCpp) {
- inputFileUrl = QStringLiteral("qrc://") + inputResourcePath;
+ inputFileUrl = "qrc://"_L1 + inputResourcePath;
saveFunction = [inputResourcePath, outputFileName](
const QV4::CompiledData::SaveableUnitPointer &unit,
const QQmlJSAotFunctionMap &aotFunctions,
QString *errorString) {
- return qSaveQmlJSUnitAsCpp(inputResourcePath, outputFileName, unit, aotFunctions,
- errorString);
+ return qSaveQmlJSUnitAsCpp(inputResourcePath, outputFileName, unit, aotFunctions, errorString);
};
} else {
@@ -223,20 +233,20 @@ int main(int argc, char **argv)
};
}
- if (inputFile.endsWith(QLatin1String(".qml"))) {
+ if (inputFile.endsWith(".qml"_L1)) {
QQmlJSCompileError error;
if (target != GenerateCpp || inputResourcePath.isEmpty() || parser.isSet(onlyBytecode)) {
if (!qCompileQmlFile(inputFile, saveFunction, nullptr, &error,
/* storeSourceLocation */ false)) {
- error.augment(QStringLiteral("Error compiling qml file: ")).print();
+ error.augment("Error compiling qml file: "_L1).print();
return EXIT_FAILURE;
}
} else {
QStringList importPaths;
if (parser.isSet(resourceOption)) {
- importPaths.append(QLatin1String(":/qt-project.org/imports"));
- importPaths.append(QLatin1String(":/qt/qml"));
+ importPaths.append("qt-project.org/imports"_L1);
+ importPaths.append("qt/qml"_L1);
};
if (parser.isSet(importPathOption))
@@ -254,39 +264,50 @@ int main(int argc, char **argv)
logger.setCategoryIgnored(qmlCompiler, false);
logger.setCategoryFatal(qmlCompiler, true);
- // By default, we're completely silent,
- // as the lcAotCompiler category default is QtFatalMsg
- const bool loggingEnabled = lcAotCompiler().isDebugEnabled()
- || lcAotCompiler().isInfoEnabled() || lcAotCompiler().isWarningEnabled()
- || lcAotCompiler().isCriticalEnabled();
- if (!loggingEnabled)
+ if (!parser.isSet(verboseOption) && !parser.isSet(warningsAreErrorsOption))
logger.setSilent(true);
QQmlJSAotCompiler cppCodeGen(
- &importer, u':' + inputResourcePath, parser.values(importsOption), &logger);
+ &importer, u':' + inputResourcePath,
+ QQmlJSUtils::cleanPaths(parser.values(importsOption)), &logger);
+
+ if (parser.isSet(dumpAotStatsOption)) {
+ QQmlJS::QQmlJSAotCompilerStats::setRecordAotStats(true);
+ QQmlJS::QQmlJSAotCompilerStats::setModuleId(parser.value(moduleIdOption));
+ }
+
+ if (parser.isSet(validateBasicBlocksOption))
+ cppCodeGen.m_flags.setFlag(QQmlJSAotCompiler::ValidateBasicBlocks);
if (!qCompileQmlFile(inputFile, saveFunction, &cppCodeGen, &error,
/* storeSourceLocation */ true)) {
- error.augment(QStringLiteral("Error compiling qml file: ")).print();
+ error.augment("Error compiling qml file: "_L1).print();
return EXIT_FAILURE;
}
QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings();
if (!warnings.isEmpty()) {
- logger.log(QStringLiteral("Type warnings occurred while compiling file:"),
+ logger.log("Type warnings occurred while compiling file:"_L1,
qmlImport, QQmlJS::SourceLocation());
logger.processMessages(warnings, qmlImport);
+ if (parser.isSet(warningsAreErrorsOption))
+ return EXIT_FAILURE;
}
+
+ if (parser.isSet(dumpAotStatsOption))
+ QQmlJS::QQmlJSAotCompilerStats::instance()->saveToDisk(outputFileName + u".aotstats"_s);
}
- } else if (inputFile.endsWith(QLatin1String(".js")) || inputFile.endsWith(QLatin1String(".mjs"))) {
+ } else if (inputFile.endsWith(".js"_L1) || inputFile.endsWith(".mjs"_L1)) {
QQmlJSCompileError error;
if (!qCompileJSFile(inputFile, inputFileUrl, saveFunction, &error)) {
- error.augment(QLatin1String("Error compiling js file: ")).print();
+ error.augment("Error compiling js file: "_L1).print();
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "Ignoring %s input file as it is not QML source code - maybe remove from QML_FILES?\n", qPrintable(inputFile));
+ if (parser.isSet(warningsAreErrorsOption))
+ return EXIT_FAILURE;
}
return EXIT_SUCCESS;
diff --git a/tools/qmldom/qmldomtool.cpp b/tools/qmldom/qmldomtool.cpp
index 30eeda3792..99c81e0a95 100644
--- a/tools/qmldom/qmldomtool.cpp
+++ b/tools/qmldom/qmldomtool.cpp
@@ -208,18 +208,18 @@ int main(int argc, char *argv[])
DomItem env(envPtr);
qDebug() << "will load\n";
if (dep != Dependencies::None)
- env.loadBuiltins();
+ envPtr->loadBuiltins();
QList<DomItem> loadedFiles(positionalArguments.size());
qsizetype iPos = 0;
for (const QString &s : std::as_const(positionalArguments)) {
- env.loadFile(
- s, QString(),
+ envPtr->loadFile(
+ FileToLoad::fromFileSystem(envPtr, s),
[&loadedFiles, iPos](Path, const DomItem &, const DomItem &newIt) {
loadedFiles[iPos] = newIt;
},
- LoadOption::DefaultLoad, fileType);
+ fileType);
}
- envPtr->loadPendingDependencies(env);
+ envPtr->loadPendingDependencies();
bool hadFailures = false;
const qsizetype largestFileSizeToCheck = 32000;
@@ -245,7 +245,7 @@ int main(int argc, char *argv[])
QDir d(rDir);
target = d.filePath(f.fileName());
}
- MutableDomItem res = qmlFile.writeOut(target, nBackups, lwOptions, &fw, checks);
+ auto res = qmlFile.writeOut(target, nBackups, lwOptions, &fw, checks);
switch (fw.status) {
case FileWriter::Status::ShouldWrite:
case FileWriter::Status::SkippedDueToFailure:
@@ -257,7 +257,7 @@ int main(int argc, char *argv[])
case FileWriter::Status::SkippedEqual:
qDebug() << "no change";
}
- hadFailures = hadFailures || !bool(res);
+ hadFailures = hadFailures || !res;
}
} else if (parser.isSet(dumpAstOption)) {
if (pathsToDump.size() > 1) {
@@ -285,7 +285,7 @@ int main(int argc, char *argv[])
};
qsizetype iPathToDump = 0;
bool globalPaths = false;
- for (auto p : pathsToDump)
+ for (const auto &p : pathsToDump)
if (p.headKind() == Path::Kind::Root)
globalPaths = true;
if (globalPaths)
diff --git a/tools/qmlformat/CMakeLists.txt b/tools/qmlformat/CMakeLists.txt
index 7675363ce4..908901b9f5 100644
--- a/tools/qmlformat/CMakeLists.txt
+++ b/tools/qmlformat/CMakeLists.txt
@@ -13,11 +13,10 @@ qt_internal_add_tool(${target_name}
TOOLS_TARGET Qml # special case
SOURCES
qmlformat.cpp
- ../shared/qqmltoolingsettings.h
- ../shared/qqmltoolingsettings.cpp
LIBRARIES
Qt::Core
Qt::QmlDomPrivate
+ Qt::QmlToolingSettingsPrivate
)
qt_internal_return_unless_building_tools()
diff --git a/tools/qmlformat/qmlformat.cpp b/tools/qmlformat/qmlformat.cpp
index b1e0bb75de..e26a6412c9 100644
--- a/tools/qmlformat/qmlformat.cpp
+++ b/tools/qmlformat/qmlformat.cpp
@@ -19,7 +19,8 @@
# include <QCommandLineParser>
#endif
-#include "../shared/qqmltoolingsettings.h"
+#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
+
using namespace QQmlJS::Dom;
@@ -45,45 +46,16 @@ struct Options
QStringList errors;
};
-bool parseFile(const QString &filename, const Options &options)
+// TODO refactor
+// Move out to the LineWriterOptions class / helper
+static LineWriterOptions composeLwOptions(const Options &options, QStringView code)
{
- DomItem env =
- DomEnvironment::create(QStringList(),
- QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
- | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
- DomItem tFile; // place where to store the loaded file
- env.loadFile(
- filename, QString(),
- [&tFile](Path, const DomItem &, const DomItem &newIt) {
- tFile = newIt; // callback called when everything is loaded that receives the loaded
- // external file pair (path, oldValue, newValue)
- },
- LoadOption::DefaultLoad);
- env.loadPendingDependencies();
- DomItem qmlFile = tFile.fileObject();
- std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>();
- if (!qmlFilePtr || !qmlFilePtr->isValid()) {
- qmlFile.iterateErrors(
- [](DomItem, ErrorMessage msg) {
- errorToQDebug(msg);
- return true;
- },
- true);
- qWarning().noquote() << "Failed to parse" << filename;
- return false;
- }
-
- // Turn AST back into source code
- if (options.verbose)
- qWarning().noquote() << "Dumping" << filename;
-
LineWriterOptions lwOptions;
lwOptions.formatOptions.indentSize = options.indentWidth;
lwOptions.formatOptions.useTabs = options.tabs;
lwOptions.updateOptions = LineWriterOptions::Update::None;
if (options.newline == "native") {
// find out current line endings...
- QStringView code = qmlFilePtr->code();
int newlineIndex = code.indexOf(QChar(u'\n'));
int crIndex = code.indexOf(QChar(u'\r'));
if (newlineIndex >= 0) {
@@ -108,37 +80,111 @@ bool parseFile(const QString &filename, const Options &options)
} else if (options.newline == "unix") {
lwOptions.lineEndings = LineWriterOptions::LineEndings::Unix;
} else {
- qWarning().noquote() << "Unknown line ending type" << options.newline;
- return false;
+ qWarning().noquote() << "Unknown line ending type" << options.newline << ", using default";
}
if (options.normalize)
lwOptions.attributesSequence = LineWriterOptions::AttributesSequence::Normalize;
else
lwOptions.attributesSequence = LineWriterOptions::AttributesSequence::Preserve;
- WriteOutChecks checks = WriteOutCheck::Default;
- if (options.force || qmlFilePtr->code().size() > 32000)
- checks = WriteOutCheck::None;
lwOptions.objectsSpacing = options.objectsSpacing;
lwOptions.functionsSpacing = options.functionsSpacing;
+ return lwOptions;
+}
- MutableDomItem res;
+static void logParsingErrors(const DomItem &fileItem, const QString &filename)
+{
+ fileItem.iterateErrors(
+ [](const DomItem &, const ErrorMessage &msg) {
+ errorToQDebug(msg);
+ return true;
+ },
+ true);
+ qWarning().noquote() << "Failed to parse" << filename;
+}
+
+// TODO
+// refactor this workaround. ExternalOWningItem is not recognized as an owning type
+// in ownerAs.
+static std::shared_ptr<ExternalOwningItem> getFileItemOwner(const DomItem &fileItem)
+{
+ std::shared_ptr<ExternalOwningItem> filePtr = nullptr;
+ switch (fileItem.internalKind()) {
+ case DomType::JsFile:
+ filePtr = fileItem.ownerAs<JsFile>();
+ break;
+ default:
+ filePtr = fileItem.ownerAs<QmlFile>();
+ break;
+ }
+ return filePtr;
+}
+
+// TODO refactor
+// Introduce better encapsulation and separation of concerns and move to DOM API
+// returns a DomItem corresponding to the loaded file and bool indicating the validity of the file
+static std::pair<DomItem, bool> parse(const QString &filename)
+{
+ auto envPtr =
+ DomEnvironment::create(QStringList(),
+ QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
+ | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
+ // placeholder for a node
+ // containing metadata (ExternalItemInfo) about the loaded file
+ DomItem fMetadataItem;
+ envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filename),
+ // callback called when everything is loaded that receives the
+ // loaded external file pair (path, oldValue, newValue)
+ [&fMetadataItem](Path, const DomItem &, const DomItem &extItemInfo) {
+ fMetadataItem = extItemInfo;
+ });
+ auto fItem = fMetadataItem.fileObject();
+ auto filePtr = getFileItemOwner(fItem);
+ return { fItem, filePtr && filePtr->isValid() };
+}
+
+static bool parseFile(const QString &filename, const Options &options)
+{
+ const auto [fileItem, validFile] = parse(filename);
+ if (!validFile) {
+ logParsingErrors(fileItem, filename);
+ return false;
+ }
+
+ // Turn AST back into source code
+ if (options.verbose)
+ qWarning().noquote() << "Dumping" << filename;
+
+ const auto &code = getFileItemOwner(fileItem)->code();
+ auto lwOptions = composeLwOptions(options, code);
+ WriteOutChecks checks = WriteOutCheck::Default;
+ //Disable writeOutChecks for some usecases
+ if (options.force ||
+ code.size() > 32000 ||
+ fileItem.internalKind() == DomType::JsFile) {
+ checks = WriteOutCheck::None;
+ }
+
+ bool res = false;
if (options.inplace) {
if (options.verbose)
qWarning().noquote() << "Writing to file" << filename;
FileWriter fw;
const unsigned numberOfBackupFiles = 0;
- res = qmlFile.writeOut(filename, numberOfBackupFiles, lwOptions, &fw, checks);
+ res = fileItem.writeOut(filename, numberOfBackupFiles, lwOptions, &fw, checks);
} else {
QFile out;
- out.open(stdout, QIODevice::WriteOnly);
- LineWriter lw([&out](QStringView s) { out.write(s.toUtf8()); }, filename, lwOptions);
- OutWriter ow(lw);
- res = qmlFile.writeOutForFile(ow, checks);
- ow.flush();
+ if (out.open(stdout, QIODevice::WriteOnly)) {
+ LineWriter lw([&out](QStringView s) { out.write(s.toUtf8()); }, filename, lwOptions);
+ OutWriter ow(lw);
+ res = fileItem.writeOutForFile(ow, checks);
+ ow.flush();
+ } else {
+ res = false;
+ }
}
- return bool(res);
+ return res;
}
Options buildCommandLineOptions(const QCoreApplication &app)
@@ -216,8 +262,7 @@ Options buildCommandLineOptions(const QCoreApplication &app)
QStringList files;
if (!parser.value("files").isEmpty()) {
QFile file(parser.value("files"));
- file.open(QIODevice::Text | QIODevice::ReadOnly);
- if (file.isOpen()) {
+ if (file.open(QIODevice::Text | QIODevice::ReadOnly)) {
QTextStream in(&file);
while (!in.atEnd()) {
QString file = in.readLine();
@@ -291,6 +336,10 @@ int main(int argc, char *argv[])
return settings.writeDefaults() ? 0 : -1;
auto getSettings = [&](const QString &file, Options options) {
+ // Perform formatting inplace if --files option is set.
+ if (!options.files.isEmpty())
+ options.inplace = true;
+
if (options.ignoreSettings || !settings.search(file))
return options;
diff --git a/tools/qmljs/CMakeLists.txt b/tools/qmljs/CMakeLists.txt
index 4a1f8488b2..40faa5d013 100644
--- a/tools/qmljs/CMakeLists.txt
+++ b/tools/qmljs/CMakeLists.txt
@@ -49,11 +49,6 @@ set_target_properties(qmljs PROPERTIES WIN32_EXECUTABLE FALSE)
## Scopes:
#####################################################################
-qt_internal_extend_target(qmljs CONDITION WIN32
- DEFINES
- NOMINMAX
-)
-
qt_internal_extend_target(qmljs CONDITION disassembler AND ((TEST_architecture_arch STREQUAL "i386") OR (TEST_architecture_arch STREQUAL "x86_64"))
DEFINES
WTF_USE_UDIS86=1
diff --git a/tools/qmljs/qmljs.cpp b/tools/qmljs/qmljs.cpp
index ae01e7560f..8a1c4d0ded 100644
--- a/tools/qmljs/qmljs.cpp
+++ b/tools/qmljs/qmljs.cpp
@@ -19,6 +19,7 @@
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QDateTime>
+#include <QtCore/qcommandlineparser.h>
#include <private/qqmljsengine_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qqmljsparser_p.h>
@@ -49,43 +50,48 @@ int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
- QStringList args = app.arguments();
- args.removeFirst();
- bool runAsQml = false;
- bool runAsModule = false;
- bool cache = false;
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.setApplicationDescription("Utility to execute scripts in QML's V4 engine");
+ parser.addVersionOption();
+ parser.addPositionalArgument("files", "Files to execute.", "[files...]");
- if (!args.isEmpty()) {
- if (args.constFirst() == QLatin1String("--jit")) {
- qputenv("QV4_JIT_CALL_THRESHOLD", QByteArray("0"));
- args.removeFirst();
- }
- if (args.constFirst() == QLatin1String("--interpret")) {
- qputenv("QV4_FORCE_INTERPRETER", QByteArray("1"));
- args.removeFirst();
- }
- if (args.constFirst() == QLatin1String("--qml")) {
- runAsQml = true;
- args.removeFirst();
- }
+ QCommandLineOption forceJit("jit", "Force JIT.");
+ parser.addOption(forceJit);
- if (args.constFirst() == QLatin1String("--module")) {
- runAsModule = true;
- args.removeFirst();
- }
+ QCommandLineOption forceInterpreter("interpret", "Force interpreter.");
+ parser.addOption(forceInterpreter);
- if (args.constFirst() == QLatin1String("--cache")) {
- cache = true;
- args.removeFirst();
- }
+ QCommandLineOption qml("qml", "Run as QML.");
+ parser.addOption(qml);
+
+ QCommandLineOption module("module", "Run as Module.");
+ parser.addOption(module);
- if (args.constFirst() == QLatin1String("--help")) {
- std::cerr << "Usage: qmljs [|--jit|--interpret|--qml] file..." << std::endl;
- return EXIT_SUCCESS;
+ QCommandLineOption cache("cache", "Use cache.");
+ parser.addOption(cache);
+
+ parser.process(app);
+
+ bool jitEnabled = false;
+
+ if (parser.isSet(forceJit)) {
+ qputenv("QV4_JIT_CALL_THRESHOLD", QByteArray("0"));
+ jitEnabled = true;
+ }
+ if (parser.isSet(forceInterpreter)) {
+ qputenv("QV4_FORCE_INTERPRETER", QByteArray("1"));
+ if (jitEnabled) {
+ std::cerr << "You cannot use 'Force JIT' and 'Force Interpreter' at the same time.";
+ return EXIT_FAILURE;
}
}
+ const bool runAsQml = parser.isSet(qml);
+ const bool runAsModule = parser.isSet(module);
+ const bool useCache = parser.isSet(cache);
+ const QStringList args = parser.positionalArguments();
QV4::ExecutionEngine vm;
@@ -94,12 +100,12 @@ int main(int argc, char *argv[])
QV4::GlobalExtensions::init(vm.globalObject, QJSEngine::ConsoleExtension | QJSEngine::GarbageCollectionExtension);
- for (const QString &fn : std::as_const(args)) {
+ for (const QString &fn : args) {
QV4::ScopedValue result(scope);
if (runAsModule) {
auto module = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath()));
if (module.compiled) {
- if (module.compiled->instantiate(&vm))
+ if (module.compiled->instantiate())
module.compiled->evaluate();
} else if (module.native) {
// Nothing to do. Native modules have no global code.
@@ -113,12 +119,12 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
}
QScopedPointer<QV4::Script> script;
- if (cache && QFile::exists(fn + QLatin1Char('c'))) {
- QQmlRefPointer<QV4::ExecutableCompilationUnit> unit
- = QV4::ExecutableCompilationUnit::create();
+ if (useCache && QFile::exists(fn + QLatin1Char('c'))) {
+ auto unit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>();
QString error;
if (unit->loadFromDisk(QUrl::fromLocalFile(fn), QFileInfo(fn).lastModified(), &error)) {
- script.reset(new QV4::Script(&vm, nullptr, unit));
+ script.reset(new QV4::Script(
+ &vm, nullptr, vm.insertCompilationUnit(std::move(unit))));
} else {
std::cout << "Error loading" << qPrintable(fn) << "from disk cache:" << qPrintable(error) << std::endl;
}
@@ -134,12 +140,13 @@ int main(int argc, char *argv[])
}
if (!scope.hasException()) {
const auto unit = script->compilationUnit;
- if (cache && unit && !(unit->unitData()->flags & QV4::CompiledData::Unit::StaticData)) {
+ if (useCache && unit && !(unit->unitData()->flags & QV4::CompiledData::Unit::StaticData)) {
if (unit->unitData()->sourceTimeStamp == 0) {
const_cast<QV4::CompiledData::Unit*>(unit->unitData())->sourceTimeStamp = QFileInfo(fn).lastModified().toMSecsSinceEpoch();
}
QString saveError;
- if (!unit->saveToDisk(QUrl::fromLocalFile(fn), &saveError)) {
+ if (!unit->baseCompilationUnit()->saveToDisk(
+ QUrl::fromLocalFile(fn), &saveError)) {
std::cout << "Error saving JS cache file: " << qPrintable(saveError) << std::endl;
}
}
diff --git a/tools/qmljsrootgen/main.cpp b/tools/qmljsrootgen/main.cpp
index 2bd54692d1..acd375144e 100644
--- a/tools/qmljsrootgen/main.cpp
+++ b/tools/qmljsrootgen/main.cpp
@@ -10,6 +10,7 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qfile.h>
+#include <QtCore/qcommandlineparser.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonarray.h>
@@ -141,8 +142,8 @@ static QString buildClass(const QJSManagedValue &value, QJsonArray *classes,
protoName = name.at(0).toUpper() + name.mid(1) + QStringLiteral("Prototype");
}
- auto it = seen->prototypes.find(protoName);
- if (it == seen->prototypes.end()) {
+ auto it = seen->prototypes.constFind(protoName);
+ if (it == seen->prototypes.cend()) {
seen->prototypes.insert(protoName, prototype.toJSValue());
buildClass(prototype, classes, seen, protoName);
} else if (!it->strictlyEquals(prototype.toJSValue())) {
@@ -311,8 +312,8 @@ static QString buildConstructor(const QJSManagedValue &constructor, QJsonArray *
Q_UNREACHABLE();
}
- auto it = seen->constructors.find(name);
- if (it == seen->constructors.end()) {
+ auto it = seen->constructors.constFind(name);
+ if (it == seen->constructors.cend()) {
seen->constructors.insert(name, constructor.toJSValue());
return buildClass(*constructed, classes, seen, name);
} else if (!constructor.strictlyEquals(QJSManagedValue(*it, constructor.engine()))) {
@@ -326,14 +327,22 @@ int main(int argc, char *argv[])
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
- QStringList args = app.arguments();
+ QCommandLineParser parser;
+ parser.addHelpOption();
+ parser.setApplicationDescription("Internal development tool.");
+ parser.addPositionalArgument("path", "Output json path.", "path");
- if (args.size() != 2) {
- qWarning().noquote() << app.applicationName() << "[output json path]";
- return 1;
+ parser.process(app);
+
+ const QStringList args = parser.positionalArguments();
+ if (auto size = args.size(); size == 0) {
+ qWarning().noquote().nospace() << app.applicationName() << ": Output path missing.";
+ return EXIT_FAILURE;
+ } else if (size >= 2) {
+ qWarning().noquote().nospace() << app.applicationName() << ": Too many output paths given. Only one allowed.";
}
- QString fileName = args.at(1);
+ const QString fileName = args.at(0);
QJSEngine engine;
engine.installExtensions(QJSEngine::AllExtensions);
diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt
index 2802d6d3af..ae3de5901d 100644
--- a/tools/qmllint/CMakeLists.txt
+++ b/tools/qmllint/CMakeLists.txt
@@ -13,11 +13,10 @@ qt_internal_add_tool(${target_name}
TOOLS_TARGET Qml # special case
SOURCES
main.cpp
- ../shared/qqmltoolingsettings.h
- ../shared/qqmltoolingsettings.cpp
LIBRARIES
Qt::CorePrivate
Qt::QmlCompilerPrivate
+ Qt::QmlToolingSettingsPrivate
)
qt_internal_return_unless_building_tools()
diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp
index 5c35c19928..da0f21acb5 100644
--- a/tools/qmllint/main.cpp
+++ b/tools/qmllint/main.cpp
@@ -1,11 +1,14 @@
// Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sergio Martins <sergio.martins@kdab.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "../shared/qqmltoolingsettings.h"
+#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
+#include <QtQmlToolingSettings/private/qqmltoolingutils_p.h>
-#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h>
#include <QtQmlCompiler/private/qqmljscompiler_p.h>
#include <QtQmlCompiler/private/qqmljslinter_p.h>
+#include <QtQmlCompiler/private/qqmljsloggingutils_p.h>
+#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h>
+#include <QtQmlCompiler/private/qqmljsutils_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfile.h>
@@ -29,10 +32,39 @@ using namespace Qt::StringLiterals;
constexpr int JSON_LOGGING_FORMAT_REVISION = 3;
+bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments)
+{
+ allArguments.reserve(arguments.size());
+ for (const QString &argument : arguments) {
+ // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
+ if (argument.startsWith(u'@')) {
+ QString optionsFile = argument;
+ optionsFile.remove(0, 1);
+ if (optionsFile.isEmpty()) {
+ qWarning().nospace() << "The @ option requires an input file";
+ return false;
+ }
+ QFile f(optionsFile);
+ if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qWarning().nospace() << "Cannot open options file specified with @";
+ return false;
+ }
+ while (!f.atEnd()) {
+ QString line = QString::fromLocal8Bit(f.readLine().trimmed());
+ if (!line.isEmpty())
+ allArguments << line;
+ }
+ } else {
+ allArguments << argument;
+ }
+ }
+ return true;
+}
+
int main(int argv, char *argc[])
{
QHashSeed::setDeterministicGlobalSeed();
- QList<QQmlJSLogger::Category> categories;
+ QList<QQmlJS::LoggerCategory> categories;
QCoreApplication app(argv, argc);
QCoreApplication::setApplicationName("qmllint");
@@ -92,6 +124,11 @@ All warnings can be set to three levels:
const QString qmlImportPathsSetting = QLatin1String("AdditionalQmlImportPaths");
settings.addOption(qmlImportPathsSetting);
+ QCommandLineOption environmentOption(
+ QStringList() << "E",
+ QLatin1String("Use the QML_IMPORT_PATH environment variable to look for QML Modules"));
+ parser.addOption(environmentOption);
+
QCommandLineOption qmlImportNoDefault(
QStringList() << "bare",
QLatin1String("Do not include default import directories or the current directory. "
@@ -150,20 +187,48 @@ All warnings can be set to three levels:
QLatin1String("directory"));
parser.addOption(pluginPathsOption);
- auto addCategory = [&](const QQmlJSLogger::Category &category) {
+ QCommandLineOption maxWarnings(
+ QStringList() << "W"
+ << "max-warnings",
+ QLatin1String("Exit with an error code if more than \"count\" many"
+ "warnings are found by qmllint. By default or if \"count\" "
+ "is -1, warnings do not cause qmllint "
+ "to return with an error exit code."),
+ "count"
+ );
+ parser.addOption(maxWarnings);
+ settings.addOption("MaxWarnings", -1);
+
+ auto levelToString = [](const QQmlJS::LoggerCategory &category) -> QString {
+ Q_ASSERT(category.isIgnored() || category.level() != QtCriticalMsg);
+ if (category.isIgnored())
+ return QStringLiteral("disable");
+
+ switch (category.level()) {
+ case QtInfoMsg:
+ return QStringLiteral("info");
+ case QtWarningMsg:
+ return QStringLiteral("warning");
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ };
+
+ auto addCategory = [&](const QQmlJS::LoggerCategory &category) {
categories.push_back(category);
- if (category.isDefault)
+ if (category.isDefault())
return;
QCommandLineOption option(
category.id().name().toString(),
- category.description
- + QStringLiteral(" (default: %1)").arg(category.levelToString()),
- QStringLiteral("level"), category.levelToString());
- if (category.ignored)
+ category.description()
+ + QStringLiteral(" (default: %1)").arg(levelToString(category)),
+ QStringLiteral("level"), levelToString(category));
+ if (category.isIgnored())
option.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(option);
- settings.addOption(QStringLiteral("Warnings/") + category.settingsName,
- category.levelToString());
+ settings.addOption(QStringLiteral("Warnings/") + category.settingsName(),
+ levelToString(category));
};
for (const auto &category : QQmlJSLogger::defaultCategories()) {
@@ -172,11 +237,16 @@ All warnings can be set to three levels:
parser.addPositionalArgument(QLatin1String("files"),
QLatin1String("list of qml or js files to verify"));
- if (!parser.parse(app.arguments())) {
- if (parser.unknownOptionNames().isEmpty()) {
- qWarning().noquote() << parser.errorText();
- return 1;
- }
+
+ QStringList arguments;
+ if (!argumentsFromCommandLineAndFile(arguments, app.arguments())) {
+ // argumentsFromCommandLine already printed any necessary warnings.
+ return 1;
+ }
+
+ if (!parser.parse(arguments)) {
+ qWarning().noquote() << parser.errorText();
+ return 1;
}
// Since we can't use QCommandLineParser::process(), we need to handle version and help manually
@@ -192,21 +262,30 @@ All warnings can be set to three levels:
auto updateLogLevels = [&]() {
for (auto &category : categories) {
- if (category.isDefault)
+ if (category.isDefault())
continue;
const QString &key = category.id().name().toString();
- const QString &settingsName = QStringLiteral("Warnings/") + category.settingsName;
+ const QString &settingsName = QStringLiteral("Warnings/") + category.settingsName();
if (parser.isSet(key) || settings.isSet(settingsName)) {
const QString value = parser.isSet(key) ? parser.value(key)
: settings.value(settingsName).toString();
// Do not try to set the levels if it's due to a default config option.
// This way we can tell which options have actually been overwritten by the user.
- if (category.levelToString() == value && !parser.isSet(key))
+ if (levelToString(category) == value && !parser.isSet(key))
continue;
- if (!category.setLevel(value)) {
+ if (value == "disable"_L1) {
+ category.setLevel(QtCriticalMsg);
+ category.setIgnored(true);
+ } else if (value == "info"_L1) {
+ category.setLevel(QtInfoMsg);
+ category.setIgnored(false);
+ } else if (value == "warning"_L1) {
+ category.setLevel(QtWarningMsg);
+ category.setIgnored(false);
+ } else {
qWarning() << "Invalid logging level" << value << "provided for"
<< category.id().name().toString()
<< "(allowed are: disable, info, warning)";
@@ -235,8 +314,8 @@ All warnings can be set to three levels:
QStringList defaultQmldirFiles;
if (parser.isSet(qmldirFilesOption)) {
- defaultQmldirFiles = parser.values(qmldirFilesOption);
- } else {
+ defaultQmldirFiles = QQmlJSUtils::cleanPaths(parser.values(qmldirFilesOption));
+ } else if (!parser.isSet(qmlImportNoDefault)){
// If nothing given explicitly, use the qmldir file from the current directory.
QFileInfo qmldirFile(QStringLiteral("qmldir"));
if (qmldirFile.isFile()) {
@@ -267,7 +346,7 @@ All warnings can be set to three levels:
QQmlJSLinter linter(qmlImportPaths, pluginPaths, useAbsolutePath);
for (const QQmlJSLinter::Plugin &plugin : linter.plugins()) {
- for (const QQmlJSLogger::Category &category : plugin.categories())
+ for (const QQmlJS::LoggerCategory &category : plugin.categories())
addCategory(category);
}
@@ -300,76 +379,109 @@ All warnings can be set to three levels:
QJsonArray jsonFiles;
for (const QString &filename : positionalArguments) {
- if (!parser.isSet(ignoreSettings)) {
+ if (!parser.isSet(ignoreSettings))
settings.search(filename);
- updateLogLevels();
+ updateLogLevels();
- const QDir fileDir = QFileInfo(filename).absoluteDir();
- auto addAbsolutePaths = [&](QStringList &list, const QStringList &entries) {
- for (const QString &file : entries)
- list << (QFileInfo(file).isAbsolute() ? file : fileDir.filePath(file));
- };
+ const QDir fileDir = QFileInfo(filename).absoluteDir();
+ auto addAbsolutePaths = [&](QStringList &list, const QStringList &entries) {
+ for (const QString &file : entries)
+ list << (QFileInfo(file).isAbsolute() ? file : fileDir.filePath(file));
+ };
- resourceFiles = defaultResourceFiles;
+ resourceFiles = defaultResourceFiles;
- addAbsolutePaths(resourceFiles, settings.value(resourceSetting).toStringList());
+ addAbsolutePaths(resourceFiles, settings.value(resourceSetting).toStringList());
- qmldirFiles = defaultQmldirFiles;
- if (settings.isSet(qmldirFilesSetting)
- && !settings.value(qmldirFilesSetting).toStringList().isEmpty()) {
- qmldirFiles = {};
- addAbsolutePaths(qmldirFiles,
- settings.value(qmldirFilesSetting).toStringList());
- }
+ qmldirFiles = defaultQmldirFiles;
+ if (settings.isSet(qmldirFilesSetting)
+ && !settings.value(qmldirFilesSetting).toStringList().isEmpty()) {
+ qmldirFiles = {};
+ addAbsolutePaths(qmldirFiles, settings.value(qmldirFilesSetting).toStringList());
+ }
+
+ if (parser.isSet(qmlImportNoDefault)
+ || (settings.isSet(qmlImportNoDefaultSetting)
+ && settings.value(qmlImportNoDefaultSetting).toBool())) {
+ qmlImportPaths = {};
+ } else {
+ qmlImportPaths = defaultImportPaths;
+ }
- if (parser.isSet(qmlImportNoDefault)
- || (settings.isSet(qmlImportNoDefaultSetting)
- && settings.value(qmlImportNoDefaultSetting).toBool())) {
- qmlImportPaths = {};
+ if (parser.isSet(qmlImportPathsOption))
+ qmlImportPaths << parser.values(qmlImportPathsOption);
+ if (parser.isSet(environmentOption)) {
+ if (silent) {
+ qmlImportPaths << qEnvironmentVariable("QML_IMPORT_PATH")
+ .split(QDir::separator(), Qt::SkipEmptyParts)
+ << qEnvironmentVariable("QML2_IMPORT_PATH")
+ .split(QDir::separator(), Qt::SkipEmptyParts);
} else {
- qmlImportPaths = defaultImportPaths;
+ if (const QStringList dirsFromEnv =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML_IMPORT_PATH"_s);
+ !dirsFromEnv.isEmpty()) {
+ qInfo().nospace().noquote()
+ << "Using import directories passed from environment variable "
+ "\"QML_IMPORT_PATH\": \""
+ << dirsFromEnv.join(u"\", \""_s) << "\".";
+ qmlImportPaths << dirsFromEnv;
+ }
+ if (const QStringList dirsFromEnv =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(
+ u"QML2_IMPORT_PATH"_s);
+ !dirsFromEnv.isEmpty()) {
+ qInfo().nospace().noquote() << "Using import directories passed from the "
+ "deprecated environment variable "
+ "\"QML2_IMPORT_PATH\": \""
+ << dirsFromEnv.join(u"\", \""_s) << "\".";
+ qmlImportPaths << dirsFromEnv;
+ }
}
+ }
- if (parser.isSet(qmlImportPathsOption))
- qmlImportPaths << parser.values(qmlImportPathsOption);
-
- addAbsolutePaths(qmlImportPaths, settings.value(qmlImportPathsSetting).toStringList());
+ addAbsolutePaths(qmlImportPaths, settings.value(qmlImportPathsSetting).toStringList());
- QSet<QString> disabledPlugins;
+ QSet<QString> disabledPlugins;
- if (parser.isSet(pluginsDisable)) {
- for (const QString &plugin : parser.values(pluginsDisable))
- disabledPlugins << plugin.toLower();
- }
+ if (parser.isSet(pluginsDisable)) {
+ for (const QString &plugin : parser.values(pluginsDisable))
+ disabledPlugins << plugin.toLower();
+ }
- if (settings.isSet(pluginsDisableSetting)) {
- for (const QString &plugin : settings.value(pluginsDisableSetting).toStringList())
- disabledPlugins << plugin.toLower();
- }
+ if (settings.isSet(pluginsDisableSetting)) {
+ for (const QString &plugin : settings.value(pluginsDisableSetting).toStringList())
+ disabledPlugins << plugin.toLower();
+ }
- linter.setPluginsEnabled(!disabledPlugins.contains("all"));
+ linter.setPluginsEnabled(!disabledPlugins.contains("all"));
- if (!linter.pluginsEnabled())
- continue;
+ if (!linter.pluginsEnabled())
+ continue;
- auto &plugins = linter.plugins();
+ auto &plugins = linter.plugins();
- for (auto &plugin : plugins)
- plugin.setEnabled(!disabledPlugins.contains(plugin.name().toLower()));
- }
+ for (auto &plugin : plugins)
+ plugin.setEnabled(!disabledPlugins.contains(plugin.name().toLower()));
const bool isFixing = parser.isSet(fixFile);
QQmlJSLinter::LintResult lintResult;
if (parser.isSet(moduleOption)) {
- lintResult = linter.lintModule(filename, silent, useJson ? &jsonFiles : nullptr);
+ lintResult = linter.lintModule(filename, silent, useJson ? &jsonFiles : nullptr,
+ qmlImportPaths, resourceFiles);
} else {
lintResult = linter.lintFile(filename, nullptr, silent || isFixing,
useJson ? &jsonFiles : nullptr, qmlImportPaths,
qmldirFiles, resourceFiles, categories);
}
- success &= (lintResult == QQmlJSLinter::LintSuccess);
+ success &= (lintResult == QQmlJSLinter::LintSuccess || lintResult == QQmlJSLinter::HasWarnings);
+ if (success && parser.isSet(maxWarnings))
+ {
+ int value = parser.value(maxWarnings).toInt();
+ if (value != -1 && value < linter.logger()->warnings().size())
+ success = false;
+ }
if (isFixing) {
if (lintResult != QQmlJSLinter::LintSuccess && lintResult != QQmlJSLinter::HasWarnings)
@@ -451,8 +563,10 @@ All warnings can be set to three levels:
QTextStream(stdout) << QString::fromUtf8(json);
} else {
QFile file(fileName);
- file.open(QFile::WriteOnly);
- file.write(json);
+ if (file.open(QFile::WriteOnly))
+ file.write(json);
+ else
+ success = false;
}
}
diff --git a/tools/qmlls/CMakeLists.txt b/tools/qmlls/CMakeLists.txt
index dcc703141e..d61a18582e 100644
--- a/tools/qmlls/CMakeLists.txt
+++ b/tools/qmlls/CMakeLists.txt
@@ -8,28 +8,16 @@
qt_internal_add_app(qmlls
TARGET_DESCRIPTION "QML Language Server"
SOURCES
- lspcustomtypes.h
- qlanguageserver.h qlanguageserver_p.h qlanguageserver.cpp
- qqmllanguageserver.h qqmllanguageserver.cpp
qmllanguageservertool.cpp
- workspace.cpp workspace.h
- textblock.h textblock.cpp
- textcursor.h textcursor.cpp
- textcursor.cpp textcursor.h
- textdocument.cpp textdocument.h
- qmllintsuggestions.h qmllintsuggestions.cpp
- textsynchronization.cpp textsynchronization.h
- qmlcompletionsupport.h qmlcompletionsupport.cpp
- qqmlcodemodel.h qqmlcodemodel.cpp
- ../shared/qqmltoolingsettings.h
- ../shared/qqmltoolingsettings.cpp
DEFINES
QT_USE_QSTRINGBUILDER
LIBRARIES
- Qt::QmlPrivate
- Qt::CorePrivate
- Qt::QmlDomPrivate
- Qt::LanguageServerPrivate
- Qt::QmlCompilerPrivate
+ Qt::QmlLSPrivate
+ Qt::QmlToolingSettingsPrivate
)
set_target_properties(qmlls PROPERTIES WIN32_EXECUTABLE FALSE)
+
+if(NOT QT6_IS_SHARED_LIBS_BUILD)
+ qt_import_plugins(qmlls INCLUDE Qt::QmlLSQuickPlugin)
+ target_link_libraries(qmlls PRIVATE Qt::QmlLSQuickPlugin)
+endif()
diff --git a/tools/qmlls/lspcustomtypes.h b/tools/qmlls/lspcustomtypes.h
deleted file mode 100644
index f46b36e578..0000000000
--- a/tools/qmlls/lspcustomtypes.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef LSPCUSTOMTYPES_H
-#define LSPCUSTOMTYPES_H
-#include <QtLanguageServer/private/qlanguageserverspec_p.h>
-
-QT_BEGIN_NAMESPACE
-
-namespace QLspSpecification {
-
-class UriToBuildDirs
-{
-public:
- QByteArray baseUri = {};
- QList<QByteArray> buildDirs = {};
-
- template<typename W>
- void walk(W &w)
- {
- field(w, "baseUri", baseUri);
- field(w, "buildDirs", buildDirs);
- }
-};
-
-namespace Notifications {
-constexpr auto AddBuildDirsMethod = "$/addBuildDirs";
-
-class AddBuildDirsParams
-{
-public:
- QList<UriToBuildDirs> buildDirsToSet = {};
-
- template<typename W>
- void walk(W &w)
- {
- field(w, "buildDirsToSet", buildDirsToSet);
- }
-};
-} // namespace Notifications
-} // namespace QLspSpecification
-
-QT_END_NAMESPACE
-
-#endif // LSPCUSTOMTYPES_H
diff --git a/tools/qmlls/qlanguageserver.cpp b/tools/qmlls/qlanguageserver.cpp
deleted file mode 100644
index 4f9c9fe77d..0000000000
--- a/tools/qmlls/qlanguageserver.cpp
+++ /dev/null
@@ -1,376 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "qlanguageserver_p.h"
-#include <QtLanguageServer/private/qlspnotifysignals_p.h>
-#include <QtJsonRpc/private/qjsonrpcprotocol_p_p.h>
-
-QT_BEGIN_NAMESPACE
-
-using namespace QLspSpecification;
-using namespace Qt::StringLiterals;
-
-Q_LOGGING_CATEGORY(lspServerLog, "qt.languageserver.server")
-
-QLanguageServerPrivate::QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h)
- : protocol(h)
-{
-}
-
-/*!
-\internal
-\class QLanguageServer
-\brief Implements a server for the language server protocol
-
-QLanguageServer is a class that uses the QLanguageServerProtocol to
-provide a server implementation.
-It handles the lifecycle management, and can be extended via
-QLanguageServerModule subclasses.
-
-The language server keeps a strictly monotonically increasing runState that can be queried
-from any thread (and is thus mutex gated), the normal run state is DidInitialize.
-
-The language server also keeps track of the task canceled by the client (or implicitly when
-shutting down, and isRequestCanceled can be called from any thread.
-*/
-
-QLanguageServer::QLanguageServer(const QJsonRpcTransport::DataHandler &h, QObject *parent)
- : QObject(*new QLanguageServerPrivate(h), parent)
-{
- Q_D(QLanguageServer);
- registerMethods(*d->protocol.typedRpc());
- d->notifySignals.registerHandlers(&d->protocol);
-}
-
-QLanguageServerProtocol *QLanguageServer::protocol()
-{
- Q_D(QLanguageServer);
- return &d->protocol;
-}
-
-QLanguageServer::RunStatus QLanguageServer::runStatus() const
-{
- const Q_D(QLanguageServer);
- QMutexLocker l(&d->mutex);
- return d->runStatus;
-}
-
-void QLanguageServer::finishSetup()
-{
- Q_D(QLanguageServer);
- RunStatus rStatus;
- {
- QMutexLocker l(&d->mutex);
- rStatus = d->runStatus;
- if (rStatus == RunStatus::NotSetup)
- d->runStatus = RunStatus::SettingUp;
- }
- if (rStatus != RunStatus::NotSetup) {
- emit lifecycleError();
- return;
- }
- emit runStatusChanged(RunStatus::SettingUp);
-
- registerHandlers(&d->protocol);
- for (auto module : d->modules)
- module->registerHandlers(this, &d->protocol);
-
- {
- QMutexLocker l(&d->mutex);
- rStatus = d->runStatus;
- if (rStatus == RunStatus::SettingUp)
- d->runStatus = RunStatus::DidSetup;
- }
- if (rStatus != RunStatus::SettingUp) {
- emit lifecycleError();
- return;
- }
- emit runStatusChanged(RunStatus::DidSetup);
-}
-
-void QLanguageServer::addServerModule(QLanguageServerModule *serverModule)
-{
- Q_D(QLanguageServer);
- Q_ASSERT(serverModule);
- RunStatus rStatus;
- {
- QMutexLocker l(&d->mutex);
- rStatus = d->runStatus;
- if (rStatus == RunStatus::NotSetup) {
- if (d->modules.contains(serverModule->name())) {
- d->modules.insert(serverModule->name(), serverModule);
- qCWarning(lspServerLog) << "Duplicate add of QLanguageServerModule named"
- << serverModule->name() << ", overwriting.";
- } else {
- d->modules.insert(serverModule->name(), serverModule);
- }
- }
- }
- if (rStatus != RunStatus::NotSetup) {
- qCWarning(lspServerLog) << "Called QLanguageServer::addServerModule after setup";
- emit lifecycleError();
- return;
- }
-}
-
-QLanguageServerModule *QLanguageServer::moduleByName(const QString &n) const
-{
- const Q_D(QLanguageServer);
- QMutexLocker l(&d->mutex);
- return d->modules.value(n);
-}
-
-QLspNotifySignals *QLanguageServer::notifySignals()
-{
- Q_D(QLanguageServer);
- return &d->notifySignals;
-}
-
-void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc)
-{
- typedRpc.installMessagePreprocessor(
- [this](const QJsonDocument &doc, const QJsonParseError &err,
- const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &responder) {
- Q_D(QLanguageServer);
- if (!doc.isObject()) {
- qCWarning(lspServerLog)
- << "non object jsonrpc message" << doc << err.errorString();
- return QJsonRpcProtocol::Processing::Stop;
- }
- bool sendErrorResponse = false;
- RunStatus rState;
- QJsonValue id = doc.object()[u"id"];
- {
- QMutexLocker l(&d->mutex);
- // the normal case is d->runStatus == RunStatus::DidInitialize
- if (d->runStatus != RunStatus::DidInitialize) {
- if (d->runStatus == RunStatus::DidSetup && !doc.isNull()
- && doc.object()[u"method"].toString()
- == QString::fromUtf8(
- QLspSpecification::Requests::InitializeMethod)) {
- return QJsonRpcProtocol::Processing::Continue;
- } else if (!doc.isNull()
- && doc.object()[u"method"].toString()
- == QString::fromUtf8(
- QLspSpecification::Notifications::ExitMethod)) {
- return QJsonRpcProtocol::Processing::Continue;
- }
- if (id.isString() || id.isDouble()) {
- sendErrorResponse = true;
- rState = d->runStatus;
- } else {
- return QJsonRpcProtocol::Processing::Stop;
- }
- }
- }
- if (!sendErrorResponse) {
- if (id.isString() || id.isDouble()) {
- QMutexLocker l(&d->mutex);
- d->requestsInProgress.insert(id, QRequestInProgress {});
- }
- return QJsonRpcProtocol::Processing::Continue;
- }
- if (rState == RunStatus::NotSetup || rState == RunStatus::DidSetup)
- responder(QJsonRpcProtocol::MessageHandler::error(
- int(QLspSpecification::ErrorCodes::ServerNotInitialized),
- u"Request on non initialized Language Server (runStatus %1): %2"_s
- .arg(int(rState))
- .arg(QString::fromUtf8(doc.toJson()))));
- else
- responder(QJsonRpcProtocol::MessageHandler::error(
- int(QLspSpecification::ErrorCodes::InvalidRequest),
- u"Method called on stopping Language Server (runStatus %1)"_s.arg(
- int(rState))));
- return QJsonRpcProtocol::Processing::Stop;
- });
- typedRpc.installOnCloseAction([this](QJsonRpc::TypedResponse::Status,
- const QJsonRpc::IdType &id, QJsonRpc::TypedRpc &) {
- Q_D(QLanguageServer);
- QJsonValue idValue = QTypedJson::toJsonValue(id);
- bool lastReq;
- {
- QMutexLocker l(&d->mutex);
- d->requestsInProgress.remove(idValue);
- lastReq = d->runStatus == RunStatus::WaitPending && d->requestsInProgress.size() <= 1;
- if (lastReq)
- d->runStatus = RunStatus::Stopping;
- }
- if (lastReq)
- executeShutdown();
- });
-}
-
-void QLanguageServer::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &serverInfo)
-{
- Q_D(QLanguageServer);
- for (auto module : std::as_const(d->modules))
- module->setupCapabilities(clientInfo, serverInfo);
-}
-
-const QLspSpecification::InitializeParams &QLanguageServer::clientInfo() const
-{
- const Q_D(QLanguageServer);
-
- if (int(runStatus()) < int(RunStatus::DidInitialize))
- qCWarning(lspServerLog) << "asked for Language Server clientInfo before initialization";
- return d->clientInfo;
-}
-
-const QLspSpecification::InitializeResult &QLanguageServer::serverInfo() const
-{
- const Q_D(QLanguageServer);
- if (int(runStatus()) < int(RunStatus::DidInitialize))
- qCWarning(lspServerLog) << "asked for Language Server serverInfo before initialization";
- return d->serverInfo;
-}
-
-void QLanguageServer::receiveData(const QByteArray &d)
-{
- protocol()->receiveData(d);
-}
-
-void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
-{
- QObject::connect(notifySignals(), &QLspNotifySignals::receivedCancelNotification, this,
- [this](const QLspSpecification::Notifications::CancelParamsType &params) {
- Q_D(QLanguageServer);
- QJsonValue id = QTypedJson::toJsonValue(params.id);
- QMutexLocker l(&d->mutex);
- if (d->requestsInProgress.contains(id))
- d->requestsInProgress[id].canceled = true;
- else
- qCWarning(lspServerLog)
- << "Ignoring cancellation of non in progress request" << id;
- });
-
- protocol->registerInitializeRequestHandler(
- [this](const QByteArray &,
- const QLspSpecification::Requests::InitializeParamsType &params,
- QLspSpecification::Responses::InitializeResponseType &&response) {
- qCDebug(lspServerLog) << "init";
- Q_D(QLanguageServer);
- RunStatus rStatus;
- {
- QMutexLocker l(&d->mutex);
- rStatus = d->runStatus;
- if (rStatus == RunStatus::DidSetup)
- d->runStatus = RunStatus::Initializing;
- }
- if (rStatus != RunStatus::DidSetup) {
- if (rStatus == RunStatus::NotSetup || rStatus == RunStatus::SettingUp)
- response.sendErrorResponse(
- int(QLspSpecification::ErrorCodes::InvalidRequest),
- u"Initialization request received on non setup language server"_s
- .toUtf8());
- else
- response.sendErrorResponse(
- int(QLspSpecification::ErrorCodes::InvalidRequest),
- u"Received multiple initialization requests"_s.toUtf8());
- emit lifecycleError();
- return;
- }
- emit runStatusChanged(RunStatus::Initializing);
- d->clientInfo = params;
- setupCapabilities(d->clientInfo, d->serverInfo);
- {
- QMutexLocker l(&d->mutex);
- d->runStatus = RunStatus::DidInitialize;
- }
- emit runStatusChanged(RunStatus::DidInitialize);
- response.sendResponse(d->serverInfo);
- });
-
- QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
- [this](const QLspSpecification::Notifications::InitializedParamsType &) {
- Q_D(QLanguageServer);
- {
- QMutexLocker l(&d->mutex);
- d->clientInitialized = true;
- }
- emit clientInitialized(this);
- });
-
- protocol->registerShutdownRequestHandler(
- [this](const QByteArray &, const QLspSpecification::Requests::ShutdownParamsType &,
- QLspSpecification::Responses::ShutdownResponseType &&response) {
- Q_D(QLanguageServer);
- RunStatus rStatus;
- bool shouldExecuteShutdown = false;
- {
- QMutexLocker l(&d->mutex);
- rStatus = d->runStatus;
- if (rStatus == RunStatus::DidInitialize) {
- d->shutdownResponse = std::move(response);
- if (d->requestsInProgress.size() <= 1) {
- d->runStatus = RunStatus::Stopping;
- shouldExecuteShutdown = true;
- } else {
- d->runStatus = RunStatus::WaitPending;
- }
- }
- }
- if (rStatus != RunStatus::DidInitialize)
- emit lifecycleError();
- else if (shouldExecuteShutdown)
- executeShutdown();
- });
-
- QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
- [this](const QLspSpecification::Notifications::ExitParamsType &) {
- if (runStatus() != RunStatus::Stopped)
- emit lifecycleError();
- else
- emit exit();
- });
-}
-
-void QLanguageServer::executeShutdown()
-{
- RunStatus rStatus = runStatus();
- if (rStatus != RunStatus::Stopping) {
- emit lifecycleError();
- return;
- }
- emit shutdown();
- QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
- {
- Q_D(QLanguageServer);
- QMutexLocker l(&d->mutex);
- rStatus = d->runStatus;
- if (rStatus == RunStatus::Stopping) {
- shutdownResponse = std::move(d->shutdownResponse);
- d->runStatus = RunStatus::Stopped;
- }
- }
- if (rStatus != RunStatus::Stopping)
- emit lifecycleError();
- else
- shutdownResponse.sendResponse(nullptr);
-}
-
-bool QLanguageServer::isRequestCanceled(const QJsonRpc::IdType &id) const
-{
- const Q_D(QLanguageServer);
- QJsonValue idVal = QTypedJson::toJsonValue(id);
- QMutexLocker l(&d->mutex);
- return d->requestsInProgress.value(idVal).canceled || d->runStatus != RunStatus::DidInitialize;
-}
-
-bool QLanguageServer::isInitialized() const
-{
- switch (runStatus()) {
- case RunStatus::NotSetup:
- case RunStatus::SettingUp:
- case RunStatus::DidSetup:
- case RunStatus::Initializing:
- return false;
- case RunStatus::DidInitialize:
- case RunStatus::WaitPending:
- case RunStatus::Stopping:
- case RunStatus::Stopped:
- break;
- }
- return true;
-}
-
-QT_END_NAMESPACE
diff --git a/tools/qmlls/qlanguageserver.h b/tools/qmlls/qlanguageserver.h
deleted file mode 100644
index d4172b192c..0000000000
--- a/tools/qmlls/qlanguageserver.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef QLANGUAGESERVER_P_H
-#define QLANGUAGESERVER_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtLanguageServer/private/qlanguageserverspec_p.h>
-#include <QtLanguageServer/private/qlanguageserverprotocol_p.h>
-#include <QtLanguageServer/private/qlspnotifysignals_p.h>
-#include <QtCore/qloggingcategory.h>
-
-QT_BEGIN_NAMESPACE
-
-class QLanguageServer;
-class QLanguageServerPrivate;
-Q_DECLARE_LOGGING_CATEGORY(lspServerLog)
-
-class QLanguageServerModule : public QObject
-{
- Q_OBJECT
-public:
- QLanguageServerModule(QObject *parent = nullptr) : QObject(parent) { }
- virtual QString name() const = 0;
- virtual void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) = 0;
- virtual void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &) = 0;
-};
-
-class QLanguageServer : public QObject
-{
- Q_OBJECT
- Q_PROPERTY(RunStatus runStatus READ runStatus NOTIFY runStatusChanged)
- Q_PROPERTY(bool isInitialized READ isInitialized)
-public:
- QLanguageServer(const QJsonRpcTransport::DataHandler &h, QObject *parent = nullptr);
- enum class RunStatus {
- NotSetup,
- SettingUp,
- DidSetup,
- Initializing,
- DidInitialize, // normal state of execution
- WaitPending,
- Stopping,
- Stopped
- };
- Q_ENUM(RunStatus)
-
- QLanguageServerProtocol *protocol();
- void finishSetup();
- void registerHandlers(QLanguageServerProtocol *protocol);
- void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &serverInfo);
- void addServerModule(QLanguageServerModule *serverModule);
- QLanguageServerModule *moduleByName(const QString &n) const;
- QLspNotifySignals *notifySignals();
-
- // API
- RunStatus runStatus() const;
- bool isInitialized() const;
- bool isRequestCanceled(const QJsonRpc::IdType &id) const;
- const QLspSpecification::InitializeParams &clientInfo() const;
- const QLspSpecification::InitializeResult &serverInfo() const;
-
-public Q_SLOTS:
- void receiveData(const QByteArray &d);
-Q_SIGNALS:
- void runStatusChanged(RunStatus);
- void clientInitialized(QLanguageServer *server);
- void shutdown();
- void exit();
- void lifecycleError();
-
-private:
- void registerMethods(QJsonRpc::TypedRpc &typedRpc);
- void executeShutdown();
- Q_DECLARE_PRIVATE(QLanguageServer)
-};
-
-QT_END_NAMESPACE
-
-#endif // QLANGUAGESERVER_P_H
diff --git a/tools/qmlls/qlanguageserver_p.h b/tools/qmlls/qlanguageserver_p.h
deleted file mode 100644
index f051309f90..0000000000
--- a/tools/qmlls/qlanguageserver_p.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef QLANGUAGESERVER_P_P_H
-#define QLANGUAGESERVER_P_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "qlanguageserver.h"
-#include <QtLanguageServer/private/qlanguageserverprotocol_p.h>
-#include <QtCore/QMutex>
-#include <QtCore/QHash>
-#include <QtCore/private/qobject_p.h>
-#include <QtLanguageServer/private/qlspnotifysignals_p.h>
-
-#include <memory>
-
-QT_BEGIN_NAMESPACE
-
-class QRequestInProgress
-{
-public:
- bool canceled = false;
-};
-
-class QLanguageServerPrivate : public QObjectPrivate
-{
-public:
- QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h);
- mutable QMutex mutex;
- // mutex gated, monotonically increasing
- QLanguageServer::RunStatus runStatus = QLanguageServer::RunStatus::NotSetup;
- QHash<QJsonValue, QRequestInProgress> requestsInProgress; // mutex gated
- bool clientInitialized = false; // mutex gated
- QLspSpecification::InitializeParams clientInfo; // immutable after runStatus > DidInitialize
- QLspSpecification::InitializeResult serverInfo; // immutable after runStatus > DidInitialize
- QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
- QHash<QString, QLanguageServerModule *> modules;
- QLanguageServerProtocol protocol;
- QLspNotifySignals notifySignals;
-};
-
-QT_END_NAMESPACE
-#endif // QLANGUAGESERVER_P_P_H
diff --git a/tools/qmlls/qmlcompletionsupport.cpp b/tools/qmlls/qmlcompletionsupport.cpp
deleted file mode 100644
index a148bc3935..0000000000
--- a/tools/qmlls/qmlcompletionsupport.cpp
+++ /dev/null
@@ -1,665 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "qmlcompletionsupport.h"
-#include "qqmllanguageserver.h"
-#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
-#include <QtCore/qthreadpool.h>
-#include <QtCore/private/qduplicatetracker_p.h>
-#include <QtCore/QRegularExpression>
-#include <QtQmlDom/private/qqmldomexternalitems_p.h>
-#include <QtQmlDom/private/qqmldomtop_p.h>
-
-QT_USE_NAMESPACE
-using namespace QLspSpecification;
-using namespace QQmlJS::Dom;
-using namespace Qt::StringLiterals;
-
-Q_LOGGING_CATEGORY(complLog, "qt.languageserver.completions")
-
-void QmlCompletionSupport::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
-{
- protocol->registerCompletionRequestHandler(
- [this](const QByteArray &, const CompletionParams &cParams,
- LSPPartialResponse<
- std::variant<QList<CompletionItem>, CompletionList, std::nullptr_t>,
- std::variant<CompletionList, QList<CompletionItem>>> &&response) {
- QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
- QmlLsp::lspUriToQmlUrl(cParams.textDocument.uri));
- if (!doc.textDocument) {
- response.sendResponse(QList<CompletionItem>());
- return;
- }
- CompletionRequest *req = new CompletionRequest;
- std::optional<int> targetVersion;
- {
- QMutexLocker l(doc.textDocument->mutex());
- targetVersion = doc.textDocument->version();
- if (!targetVersion) {
- qCWarning(complLog) << "no target version for completions in "
- << QString::fromUtf8(cParams.textDocument.uri);
- }
- req->code = doc.textDocument->toPlainText();
- }
- req->minVersion = (targetVersion ? *targetVersion : 0);
- req->response = std::move(response);
- req->completionParams = cParams;
- {
- QMutexLocker l(&m_mutex);
- m_completions.insert(req->completionParams.textDocument.uri, req);
- }
- if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= req->minVersion)
- updatedSnapshot(QmlLsp::lspUriToQmlUrl(req->completionParams.textDocument.uri));
- });
- protocol->registerCompletionItemResolveRequestHandler(
- [](const QByteArray &, const CompletionItem &cParams,
- LSPResponse<CompletionItem> &&response) { response.sendResponse(cParams); });
-}
-
-QmlCompletionSupport::QmlCompletionSupport(QmlLsp::QQmlCodeModel *codeModel)
- : m_codeModel(codeModel)
-{
- QObject::connect(m_codeModel, &QmlLsp::QQmlCodeModel::updatedSnapshot, this,
- &QmlCompletionSupport::updatedSnapshot);
-}
-
-QmlCompletionSupport::~QmlCompletionSupport()
-{
- QMutexLocker l(&m_mutex);
- qDeleteAll(m_completions);
- m_completions.clear();
-}
-
-QString QmlCompletionSupport::name() const
-{
- return u"QmlCompletionSupport"_s;
-}
-
-void QmlCompletionSupport::setupCapabilities(
- const QLspSpecification::InitializeParams &,
- QLspSpecification::InitializeResult &serverCapabilities)
-{
- QLspSpecification::CompletionOptions cOptions;
- if (serverCapabilities.capabilities.completionProvider)
- cOptions = *serverCapabilities.capabilities.completionProvider;
- cOptions.resolveProvider = false;
- cOptions.triggerCharacters = QList<QByteArray>({ QByteArray(".") });
- serverCapabilities.capabilities.completionProvider = cOptions;
-}
-
-void QmlCompletionSupport::updatedSnapshot(const QByteArray &url)
-{
- QmlLsp::OpenDocumentSnapshot doc = m_codeModel->snapshotByUrl(url);
- QList<CompletionRequest *> toCompl;
- {
- QMutexLocker l(&m_mutex);
- for (auto [it, end] = m_completions.equal_range(url); it != end; ++it) {
- if (doc.docVersion && it.value()->minVersion <= *doc.docVersion)
- toCompl.append(it.value());
- }
- if (!m_completions.isEmpty())
- qCDebug(complLog) << "updated " << QString::fromUtf8(url) << " v"
- << (doc.docVersion ? (*doc.docVersion) : -1) << ", completing"
- << m_completions.size() << "/" << m_completions.size();
- for (auto req : toCompl)
- m_completions.remove(url, req);
- }
- for (auto it = toCompl.rbegin(), end = toCompl.rend(); it != end; ++it) {
- CompletionRequest *req = *it;
- QThreadPool::globalInstance()->start([req, doc]() mutable {
- req->sendCompletions(doc);
- delete req;
- });
- }
-}
-
-struct ItemLocation
-{
- int depth = -1;
- DomItem domItem;
- FileLocations::Tree fileLocation;
-};
-
-QString CompletionRequest::urlAndPos() const
-{
- return QString::fromUtf8(completionParams.textDocument.uri) + u":"
- + QString::number(completionParams.position.line) + u":"
- + QString::number(completionParams.position.character);
-}
-
-// return the position of 0 based line and char offsets, never goes to the "next" line, but might
-// return the position of the \n or \r that starts the next line (never the one that starts line)
-static qsizetype posAfterLineChar(QString code, int line, int character)
-{
- int targetLine = line;
- qsizetype i = 0;
- while (i != code.size() && targetLine != 0) {
- QChar c = code.at(i++);
- if (c == u'\n') {
- --targetLine;
- }
- if (c == u'\r') {
- if (i != code.size() && code.at(i) == u'\n')
- ++i;
- --targetLine;
- }
- }
- qsizetype leftChars = character;
- while (i != code.size() && leftChars) {
- QChar c = code.at(i);
- if (c == u'\n' || c == u'\r')
- break;
- ++i;
- if (!c.isLowSurrogate())
- --leftChars;
- }
- return i;
-}
-
-static QList<ItemLocation> findLastItemsContaining(DomItem file, int line, int character)
-{
- QList<ItemLocation> itemsFound;
- std::shared_ptr<QmlFile> filePtr = file.ownerAs<QmlFile>();
- if (!filePtr)
- return itemsFound;
- FileLocations::Tree t = filePtr->fileLocationsTree();
- Q_ASSERT(t);
- QString code = filePtr->code(); // do something more advanced wrt to changes wrt to this->code?
- if (code.isEmpty())
- qCWarning(complLog) << "no code";
- QList<ItemLocation> toDo;
- qsizetype targetPos = posAfterLineChar(code, line, character);
- Q_ASSERT(targetPos >= 0);
- auto containsTarget = [targetPos](QQmlJS::SourceLocation l) {
- if constexpr (sizeof(qsizetype) <= sizeof(quint32)) {
- return l.begin() <= quint32(targetPos) && quint32(targetPos) < l.end();
- } else {
- return l.begin() <= targetPos && targetPos < l.end();
- }
- };
- if (containsTarget(t->info().fullRegion)) {
- ItemLocation loc;
- loc.depth = 0;
- loc.domItem = file;
- loc.fileLocation = t;
- toDo.append(loc);
- }
- while (!toDo.isEmpty()) {
- ItemLocation iLoc = toDo.last();
- toDo.removeLast();
- if (itemsFound.isEmpty() || itemsFound.constFirst().depth <= iLoc.depth) {
- if (!itemsFound.isEmpty() && itemsFound.constFirst().depth < iLoc.depth)
- itemsFound.clear();
- itemsFound.append(iLoc);
- }
- auto subEls = iLoc.fileLocation->subItems();
- for (auto it = subEls.begin(); it != subEls.end(); ++it) {
- auto subLoc = std::static_pointer_cast<AttachedInfoT<FileLocations>>(it.value());
- Q_ASSERT(subLoc);
- if (containsTarget(subLoc->info().fullRegion)) {
- ItemLocation subItem;
- subItem.depth = iLoc.depth + 1;
- subItem.domItem = iLoc.domItem.path(it.key());
- subItem.fileLocation = subLoc;
- toDo.append(subItem);
- }
- }
- }
- return itemsFound;
-}
-
-// finds the filter string, the base (for fully qualified accesses) and the whole string
-// just before pos in code
-struct CompletionContextStrings
-{
- CompletionContextStrings(QString code, qsizetype pos);
-
-public:
- // line up until pos
- QStringView preLine() const
- {
- return QStringView(m_code).mid(m_lineStart, m_pos - m_lineStart);
- }
- // the part used to filter the completion (normally actual filtering is left to the client)
- QStringView filterChars() const
- {
- return QStringView(m_code).mid(m_filterStart, m_pos - m_filterStart);
- }
- // the base part (qualified access)
- QStringView base() const
- {
- return QStringView(m_code).mid(m_baseStart, m_filterStart - m_baseStart);
- }
- // if we are at line start
- bool atLineStart() const { return m_atLineStart; }
-
-private:
- QString m_code; // the current code
- qsizetype m_pos = {}; // current position of the cursor
- qsizetype m_filterStart = {}; // start of the characters that are used to filter the suggestions
- qsizetype m_lineStart = {}; // start of the current line
- qsizetype m_baseStart = {}; // start of the dotted expression that ends at the cursor position
- bool m_atLineStart = {}; // if there are only spaces before base
-};
-
-CompletionContextStrings::CompletionContextStrings(QString code, qsizetype pos)
- : m_code(code), m_pos(pos)
-{
- // computes the context just before pos in code.
- // After this code all the values of all the attributes should be correct (see above)
- // handle also letter or numbers represented a surrogate pairs?
- m_filterStart = m_pos;
- while (m_filterStart != 0) {
- QChar c = code.at(m_filterStart - 1);
- if (!c.isLetterOrNumber() && c != u'_')
- break;
- else
- --m_filterStart;
- }
- // handle spaces?
- m_baseStart = m_filterStart;
- while (m_baseStart != 0) {
- QChar c = code.at(m_baseStart - 1);
- if (c != u'.' || m_baseStart == 1)
- break;
- c = code.at(m_baseStart - 2);
- if (!c.isLetterOrNumber() && c != u'_')
- break;
- qsizetype baseEnd = --m_baseStart;
- while (m_baseStart != 0) {
- QChar c = code.at(m_baseStart - 1);
- if (!c.isLetterOrNumber() && c != u'_')
- break;
- else
- --m_baseStart;
- }
- if (m_baseStart == baseEnd)
- break;
- }
- m_atLineStart = true;
- m_lineStart = m_baseStart;
- while (m_lineStart != 0) {
- QChar c = code.at(m_lineStart - 1);
- if (c == u'\n' || c == '\r')
- break;
- if (!c.isSpace())
- m_atLineStart = false;
- --m_lineStart;
- }
-}
-
-enum class TypeCompletionsType { None, Types, TypesAndAttributes };
-
-enum class FunctionCompletion { None, Declaration };
-
-enum class ImportCompletionType { None, Module, Version };
-
-void CompletionRequest::sendCompletions(QmlLsp::OpenDocumentSnapshot &doc)
-{
- QList<CompletionItem> res = completions(doc);
- response.sendResponse(res);
-}
-
-static QList<CompletionItem> importCompletions(DomItem &file, const CompletionContextStrings &ctx)
-{
- // returns completions for import statements, ctx is supposed to be in an import statement
- QList<CompletionItem> res;
- ImportCompletionType importCompletionType = ImportCompletionType::None;
- QRegularExpression spaceRe(uR"(\W+)"_s);
- QList<QStringView> linePieces = ctx.preLine().split(spaceRe, Qt::SkipEmptyParts);
- qsizetype effectiveLength = linePieces.size()
- + ((!ctx.preLine().isEmpty() && ctx.preLine().last().isSpace()) ? 1 : 0);
- if (effectiveLength < 2) {
- CompletionItem comp;
- comp.label = "import";
- comp.kind = int(CompletionItemKind::Keyword);
- res.append(comp);
- }
- if (linePieces.isEmpty() || linePieces.first() != u"import")
- return res;
- if (effectiveLength == 2) {
- // the cursor is after the import, possibly in a partial module name
- importCompletionType = ImportCompletionType::Module;
- } else if (effectiveLength == 3) {
- if (linePieces.last() != u"as") {
- // the cursor is after the module, possibly in a partial version token (or partial as)
- CompletionItem comp;
- comp.label = "as";
- comp.kind = int(CompletionItemKind::Keyword);
- res.append(comp);
- importCompletionType = ImportCompletionType::Version;
- }
- }
- DomItem env = file.environment();
- if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
- switch (importCompletionType) {
- case ImportCompletionType::None:
- break;
- case ImportCompletionType::Module: {
- QDuplicateTracker<QString> modulesSeen;
- for (const QString &uri : envPtr->moduleIndexUris(env)) {
- QStringView base = ctx.base(); // if we allow spaces we should get rid of them
- if (uri.startsWith(base)) {
- QStringList rest = uri.mid(base.size()).split(u'.');
- if (rest.isEmpty())
- continue;
-
- const QString label = rest.first();
- if (!modulesSeen.hasSeen(label)) {
- CompletionItem comp;
- comp.label = label.toUtf8();
- comp.kind = int(CompletionItemKind::Module);
- res.append(comp);
- }
- }
- }
- break;
- }
- case ImportCompletionType::Version:
- if (ctx.base().isEmpty()) {
- for (int majorV :
- envPtr->moduleIndexMajorVersions(env, linePieces.at(1).toString())) {
- CompletionItem comp;
- comp.label = QString::number(majorV).toUtf8();
- comp.kind = int(CompletionItemKind::Constant);
- res.append(comp);
- }
- } else {
- bool hasMajorVersion = ctx.base().endsWith(u'.');
- int majorV = -1;
- if (hasMajorVersion)
- majorV = ctx.base().mid(0, ctx.base().size() - 1).toInt(&hasMajorVersion);
- if (!hasMajorVersion)
- break;
- if (std::shared_ptr<ModuleIndex> mIndex =
- envPtr->moduleIndexWithUri(env, linePieces.at(1).toString(), majorV)) {
- for (int minorV : mIndex->minorVersions()) {
- CompletionItem comp;
- comp.label = QString::number(minorV).toUtf8();
- comp.kind = int(CompletionItemKind::Constant);
- res.append(comp);
- }
- }
- }
- break;
- }
- }
- return res;
-}
-
-static QList<CompletionItem> idsCompletions(DomItem component)
-{
- qCDebug(complLog) << "adding ids completions";
- QList<CompletionItem> res;
- for (const QString &k : component.field(Fields::ids).keys()) {
- CompletionItem comp;
- comp.label = k.toUtf8();
- comp.kind = int(CompletionItemKind::Value);
- res.append(comp);
- }
- return res;
-}
-
-static QList<CompletionItem> bindingsCompletions(DomItem &containingObject)
-{
- // returns valid bindings completions (i.e. reachable properties and signal handlers)
- QList<CompletionItem> res;
- qCDebug(complLog) << "binding completions";
- containingObject.visitPrototypeChain(
- [&res](DomItem &it) {
- qCDebug(complLog) << "prototypeChain" << it.internalKindStr() << it.canonicalPath();
- if (const QmlObject *itPtr = it.as<QmlObject>()) {
- // signal handlers
- auto methods = itPtr->methods();
- auto it = methods.cbegin();
- while (it != methods.cend()) {
- if (it.value().methodType == MethodInfo::MethodType::Signal) {
- CompletionItem comp;
- QString signal = it.key();
- comp.label =
- (u"on"_s + signal.at(0).toUpper() + signal.mid(1)).toUtf8();
- res.append(comp);
- }
- ++it;
- }
- // properties that can be bound
- auto pDefs = itPtr->propertyDefs();
- for (auto it2 = pDefs.keyBegin(); it2 != pDefs.keyEnd(); ++it2) {
- qCDebug(complLog) << "adding property" << *it2;
- CompletionItem comp;
- comp.label = it2->toUtf8();
- comp.insertText = (*it2 + u": "_s).toUtf8();
- comp.kind = int(CompletionItemKind::Property);
- res.append(comp);
- }
- }
- return true;
- },
- VisitPrototypesOption::Normal);
- return res;
-}
-
-static QList<CompletionItem> reachableSymbols(DomItem &context, const CompletionContextStrings &ctx,
- TypeCompletionsType typeCompletionType,
- FunctionCompletion completeMethodCalls)
-{
- // returns completions for the reachable types or attributes from context
- QList<CompletionItem> res;
- QMap<CompletionItemKind, QSet<QString>> symbols;
- QSet<quintptr> visited;
- QList<Path> visitedRefs;
- auto addLocalSymbols = [&res, typeCompletionType, completeMethodCalls, &symbols](DomItem &el) {
- switch (typeCompletionType) {
- case TypeCompletionsType::None:
- return false;
- case TypeCompletionsType::Types:
- switch (el.internalKind()) {
- case DomType::ImportScope: {
- const QSet<QString> localSymbols = el.localSymbolNames(
- LocalSymbolsType::QmlTypes | LocalSymbolsType::Namespaces);
- qCDebug(complLog) << "adding local symbols of:" << el.internalKindStr()
- << el.canonicalPath() << localSymbols;
- symbols[CompletionItemKind::Class] += localSymbols;
- break;
- }
- default: {
- qCDebug(complLog) << "skipping local symbols for non type" << el.internalKindStr()
- << el.canonicalPath();
- break;
- }
- }
- break;
- case TypeCompletionsType::TypesAndAttributes:
- auto localSymbols = el.localSymbolNames(LocalSymbolsType::All);
- if (const QmlObject *elPtr = el.as<QmlObject>()) {
- auto methods = elPtr->methods();
- auto it = methods.cbegin();
- while (it != methods.cend()) {
- localSymbols.remove(it.key());
- if (completeMethodCalls == FunctionCompletion::Declaration) {
- QStringList parameters;
- for (const MethodParameter &pInfo : std::as_const(it->parameters)) {
- QStringList param;
- if (!pInfo.typeName.isEmpty())
- param << pInfo.typeName;
- if (!pInfo.name.isEmpty())
- param << pInfo.name;
- if (pInfo.defaultValue) {
- param << u"= " + pInfo.defaultValue->code();
- }
- parameters.append(param.join(' '));
- }
-
- QString commentsStr;
-
- if (!it->comments.regionComments.isEmpty()) {
- for (const Comment &c : it->comments.regionComments[0].preComments) {
- commentsStr += c.rawComment().toString().trimmed() + '\n';
- }
- }
-
- CompletionItem comp;
- comp.documentation =
- u"%1%2(%3)"_s.arg(commentsStr, it.key(), parameters.join(u", "))
- .toUtf8();
- comp.label = (it.key() + u"()").toUtf8();
- comp.kind = int(CompletionItemKind::Function);
-
- if (it->typeName.isEmpty())
- comp.detail = "returns void";
- else
- comp.detail = (u"returns "_s + it->typeName).toUtf8();
-
- // Only append full bracket if there are no parameters
- if (it->parameters.isEmpty())
- comp.insertText = comp.label;
- else
- // add snippet support?
- comp.insertText = (it.key() + u"(").toUtf8();
-
- res.append(comp);
- }
- ++it;
- }
- }
- qCDebug(complLog) << "adding local symbols of:" << el.internalKindStr()
- << el.canonicalPath() << localSymbols;
- symbols[CompletionItemKind::Field] += localSymbols;
- break;
- }
- return true;
- };
- if (ctx.base().isEmpty()) {
- if (typeCompletionType != TypeCompletionsType::None) {
- qCDebug(complLog) << "adding symbols reachable from:" << context.internalKindStr()
- << context.canonicalPath();
- DomItem it = context.proceedToScope();
- it.visitScopeChain(addLocalSymbols, LookupOption::Normal, &defaultErrorHandler,
- &visited, &visitedRefs);
- }
- } else {
- QList<QStringView> baseItems = ctx.base().split(u'.', Qt::SkipEmptyParts);
- Q_ASSERT(!baseItems.isEmpty());
- auto addReachableSymbols = [&visited, &visitedRefs, &addLocalSymbols](Path,
- DomItem &it) -> bool {
- qCDebug(complLog) << "adding directly accessible symbols of" << it.internalKindStr()
- << it.canonicalPath();
- it.visitDirectAccessibleScopes(addLocalSymbols, VisitPrototypesOption::Normal,
- &defaultErrorHandler, &visited, &visitedRefs);
- return true;
- };
- Path toSearch = Paths::lookupSymbolPath(ctx.base().toString().chopped(1));
- context.resolve(toSearch, addReachableSymbols, &defaultErrorHandler);
- // add attached types? technically we should...
- }
- for (auto symbolKinds = symbols.constBegin(); symbolKinds != symbols.constEnd();
- ++symbolKinds) {
- for (auto symbol = symbolKinds.value().constBegin();
- symbol != symbolKinds.value().constEnd(); ++symbol) {
- CompletionItem comp;
- comp.label = symbol->toUtf8();
- comp.kind = int(symbolKinds.key());
- res.append(comp);
- }
- }
- return res;
-}
-
-QList<CompletionItem> CompletionRequest::completions(QmlLsp::OpenDocumentSnapshot &doc) const
-{
- QList<CompletionItem> res;
- if (!doc.validDoc) {
- qCWarning(complLog) << "No valid document for completions for "
- << QString::fromUtf8(completionParams.textDocument.uri);
- // try to add some import and global completions?
- return res;
- }
- if (!doc.docVersion || *doc.docVersion < minVersion) {
- qCWarning(complLog) << "sendCompletions on older doc version";
- } else if (!doc.validDocVersion || *doc.validDocVersion < minVersion) {
- qCWarning(complLog) << "using outdated valid doc, position might be incorrect";
- }
- DomItem file = doc.validDoc.fileObject(QQmlJS::Dom::GoTo::MostLikely);
- // clear reference cache to resolve latest versions (use a local env instead?)
- if (std::shared_ptr<DomEnvironment> envPtr = file.environment().ownerAs<DomEnvironment>())
- envPtr->clearReferenceCache();
- qsizetype pos = posAfterLineChar(code, completionParams.position.line,
- completionParams.position.character);
- CompletionContextStrings ctx(code, pos);
- QList<ItemLocation> itemsFound =
- findLastItemsContaining(file, completionParams.position.line,
- completionParams.position.character - ctx.filterChars().size());
- if (itemsFound.size() > 1) {
- QStringList paths;
- for (auto &it : itemsFound)
- paths.append(it.domItem.canonicalPath().toString());
- qCWarning(complLog) << "Multiple elements of " << urlAndPos()
- << " at the same depth:" << paths << "(using first)";
- }
- DomItem currentItem;
- if (!itemsFound.isEmpty())
- currentItem = itemsFound.first().domItem;
- qCDebug(complLog) << "Completion at " << urlAndPos() << " " << completionParams.position.line
- << ":" << completionParams.position.character << "offset:" << pos
- << "base:" << ctx.base() << "filter:" << ctx.filterChars()
- << "lastVersion:" << (doc.docVersion ? (*doc.docVersion) : -1)
- << "validVersion:" << (doc.validDocVersion ? (*doc.validDocVersion) : -1)
- << "in" << currentItem.internalKindStr() << currentItem.canonicalPath();
- DomItem containingObject = currentItem.qmlObject();
- TypeCompletionsType typeCompletionType = TypeCompletionsType::None;
- FunctionCompletion methodCompletion = FunctionCompletion::Declaration;
-
- if (!containingObject) {
- methodCompletion = FunctionCompletion::None;
- // global completions
- if (ctx.atLineStart()) {
- if (ctx.base().isEmpty()) {
- {
- CompletionItem comp;
- comp.label = "pragma";
- comp.kind = int(CompletionItemKind::Keyword);
- res.append(comp);
- }
- }
- typeCompletionType = TypeCompletionsType::Types;
- }
- // Import completion
- res += importCompletions(file, ctx);
- } else {
- methodCompletion = FunctionCompletion::Declaration;
- bool addIds = false;
-
- if (ctx.atLineStart() && currentItem.internalKind() != DomType::ScriptExpression
- && currentItem.internalKind() != DomType::List) {
- // add bindings
- methodCompletion = FunctionCompletion::None;
- if (ctx.base().isEmpty()) {
- for (const QStringView &s : std::array<QStringView, 5>(
- { u"property", u"readonly", u"default", u"signal", u"function" })) {
- CompletionItem comp;
- comp.label = s.toUtf8();
- comp.kind = int(CompletionItemKind::Keyword);
- res.append(comp);
- }
- res += bindingsCompletions(containingObject);
- typeCompletionType = TypeCompletionsType::Types;
- } else {
- // handle value types later with type expansion
- typeCompletionType = TypeCompletionsType::TypesAndAttributes;
- }
- } else {
- addIds = true;
- typeCompletionType = TypeCompletionsType::TypesAndAttributes;
- }
- if (addIds) {
- res += idsCompletions(containingObject.component());
- }
- }
-
- DomItem context = containingObject;
- if (!context)
- context = file;
- // adds types and attributes
- res += reachableSymbols(context, ctx, typeCompletionType, methodCompletion);
- return res;
-}
diff --git a/tools/qmlls/qmlcompletionsupport.h b/tools/qmlls/qmlcompletionsupport.h
deleted file mode 100644
index ce149667bd..0000000000
--- a/tools/qmlls/qmlcompletionsupport.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#ifndef QMLCOMPLETIONSUPPORT_H
-#define QMLCOMPLETIONSUPPORT_H
-
-#include "qlanguageserver.h"
-#include "qqmlcodemodel.h"
-#include <QtCore/qmutex.h>
-#include <QtCore/qhash.h>
-
-struct CompletionRequest
-{
- int minVersion;
- QString code;
- QLspSpecification::CompletionParams completionParams;
- QLspSpecification::LSPPartialResponse<
- std::variant<QList<QLspSpecification::CompletionItem>,
- QLspSpecification::CompletionList, std::nullptr_t>,
- std::variant<QLspSpecification::CompletionList,
- QList<QLspSpecification::CompletionItem>>>
- response;
- void sendCompletions(QmlLsp::OpenDocumentSnapshot &);
- QString urlAndPos() const;
- QList<QLspSpecification::CompletionItem> completions(QmlLsp::OpenDocumentSnapshot &doc) const;
-};
-
-class QmlCompletionSupport : public QLanguageServerModule
-{
- Q_OBJECT
-public:
- QmlCompletionSupport(QmlLsp::QQmlCodeModel *codeModel);
- ~QmlCompletionSupport();
- QString name() const override;
- void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
- void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &) override;
-public Q_SLOTS:
- void updatedSnapshot(const QByteArray &uri);
-
-private:
- QmlLsp::QQmlCodeModel *m_codeModel;
- QMutex m_mutex;
- QMultiHash<QString, CompletionRequest *> m_completions;
-};
-
-#endif // QMLCOMPLETIONSUPPORT_H
diff --git a/tools/qmlls/qmllanguageservertool.cpp b/tools/qmlls/qmllanguageservertool.cpp
index 896e0c049c..aa69b058bc 100644
--- a/tools/qmlls/qmllanguageservertool.cpp
+++ b/tools/qmlls/qmllanguageservertool.cpp
@@ -1,12 +1,14 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "qqmllanguageserver.h"
+
+#include <QtQmlLS/private/qqmllanguageserver_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qfile.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qcoreapplication.h>
-#include "../shared/qqmltoolingsettings.h"
+#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
+#include <QtQmlToolingSettings/private/qqmltoolingutils_p.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qjsonarray.h>
@@ -48,47 +50,83 @@ class StdinReader : public QObject
{
Q_OBJECT
public:
- void run()
- {
- auto guard = qScopeGuard([this]() { emit eof(); });
- const constexpr qsizetype bufSize = 1024;
- qsizetype bytesInBuf = 0;
- char bufferData[2 * bufSize];
- char *buffer = static_cast<char *>(bufferData);
-
- auto trySend = [this, &bytesInBuf, buffer]() {
- if (bytesInBuf == 0)
- return;
- qsizetype toSend = bytesInBuf;
- bytesInBuf = 0;
- QByteArray dataToSend(buffer, toSend);
- emit receivedData(dataToSend);
- };
- QHttpMessageStreamParser streamParser(
+ StdinReader()
+ : m_streamReader(
[](const QByteArray &, const QByteArray &) { /* just a header, do nothing */ },
- [&trySend](const QByteArray &) {
+ [this](const QByteArray &) {
+ // stop reading until we are sure that the server is not shutting down
+ m_isReading = false;
+
// message body
- trySend();
+ m_shouldSendData = true;
},
- [&trySend](QtMsgType, QString) {
+ [this](QtMsgType, QString) {
// there was an error
- trySend();
+ m_shouldSendData = true;
},
- QHttpMessageStreamParser::UNBUFFERED);
-
- while (std::cin.get(buffer[bytesInBuf])) { // should poll/select and process events
- qsizetype readNow = std::cin.readsome(buffer + bytesInBuf + 1, bufSize) + 1;
- QByteArray toAdd(buffer + bytesInBuf, readNow);
- bytesInBuf += readNow;
- if (bytesInBuf >= bufSize)
- trySend();
- streamParser.receiveData(toAdd);
- }
- trySend();
+ QHttpMessageStreamParser::UNBUFFERED)
+ {
}
+
+ void sendData()
+ {
+ const bool isEndOfMessage = !m_isReading && !m_hasEof;
+ const qsizetype toSend = m_bytesInBuf;
+ m_bytesInBuf = 0;
+ const QByteArray dataToSend(m_buffer, toSend);
+ emit receivedData(dataToSend, isEndOfMessage);
+ }
+
+private:
+ const static constexpr qsizetype s_bufSize = 1024;
+ qsizetype m_bytesInBuf = 0;
+ char m_buffer[2 * s_bufSize] = {};
+ QHttpMessageStreamParser m_streamReader;
+ /*!
+ \internal
+ Indicates if the current message is not read out entirely.
+ */
+ bool m_isReading = true;
+ /*!
+ \internal
+ Indicates if an EOF was encountered. No more data can be read after an EOF.
+ */
+ bool m_hasEof = false;
+ /*!
+ \internal
+ Indicates whether sendData() should be called or not.
+ */
+ bool m_shouldSendData = false;
signals:
- void receivedData(const QByteArray &data);
+ void receivedData(const QByteArray &data, bool canRequestMoreData);
void eof();
+public slots:
+ void readNextMessage()
+ {
+ if (m_hasEof)
+ return;
+ m_isReading = true;
+ // Try to fill up the buffer as much as possible before calling the queued signal:
+ // each loop iteration might read only one character from std::in in the worstcase, this
+ // happens for example on macos.
+ while (m_isReading) {
+ // block while waiting for some data
+ if (!std::cin.get(m_buffer[m_bytesInBuf])) {
+ m_hasEof = true;
+ emit eof();
+ return;
+ }
+ // see if more data is available and fill the buffer with it
+ qsizetype readNow = std::cin.readsome(m_buffer + m_bytesInBuf + 1, s_bufSize) + 1;
+ QByteArray toAdd(m_buffer + m_bytesInBuf, readNow);
+ m_bytesInBuf += readNow;
+ m_streamReader.receiveData(std::move(toAdd));
+
+ m_shouldSendData |= m_bytesInBuf >= s_bufSize;
+ if (std::exchange(m_shouldSendData, false))
+ sendData();
+ }
+ }
};
// To debug:
@@ -131,7 +169,7 @@ int main(int argv, char *argc[])
QCoreApplication app(argv, argc);
QCoreApplication::setApplicationName("qmlls");
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
-#if QT_CONFIG(commandlineparser)
+
QCommandLineParser parser;
QQmlToolingSettings settings(QLatin1String("qmlls"));
parser.setApplicationDescription(QLatin1String(R"(QML languageserver)"));
@@ -163,6 +201,17 @@ int main(int argv, char *argc[])
parser.addOption(buildDirOption);
settings.addOption(buildDir);
+ QString qmlImportPath = QStringLiteral(u"qml-import-path");
+ QCommandLineOption qmlImportPathOption(
+ QStringList() << "I", QLatin1String("Look for QML modules in the specified directory"),
+ qmlImportPath);
+ parser.addOption(qmlImportPathOption);
+
+ QCommandLineOption environmentOption(
+ QStringList() << "E",
+ QLatin1String("Use the QML_IMPORT_PATH environment variable to look for QML Modules"));
+ parser.addOption(environmentOption);
+
QCommandLineOption writeDefaultsOption(
QStringList() << "write-defaults",
QLatin1String("Writes defaults settings to .qmlls.ini and exits (Warning: This "
@@ -174,6 +223,17 @@ int main(int argv, char *argc[])
"command line options into consideration"));
parser.addOption(ignoreSettings);
+ QCommandLineOption noCMakeCallsOption(
+ QStringList() << "no-cmake-calls",
+ QLatin1String("Disables automatic CMake rebuilds and C++ file watching."));
+ parser.addOption(noCMakeCallsOption);
+ settings.addOption("no-cmake-calls", "false");
+
+ QCommandLineOption docDir(QStringList() << "p",
+ QLatin1String("Documentation path to use for the documentation hints feature"));
+ parser.addOption(docDir);
+ settings.addOption("docDir");
+
parser.process(app);
if (parser.isSet(writeDefaultsOption)) {
@@ -203,7 +263,6 @@ int main(int argv, char *argc[])
QThread::sleep(waitSeconds);
qDebug() << "starting";
}
-#endif
QMutex writeMutex;
QQmlLanguageServer qmlServer(
[&writeMutex](const QByteArray &data) {
@@ -212,19 +271,100 @@ int main(int argv, char *argc[])
std::cout.flush();
},
(parser.isSet(ignoreSettings) ? nullptr : &settings));
- if (parser.isSet(buildDirOption))
- qmlServer.codeModel()->setBuildPathsForRootUrl(QByteArray(), parser.values(buildDirOption));
+
+ if (parser.isSet(docDir))
+ qmlServer.codeModel()->setDocumentationRootPath(parser.value(docDir).toUtf8());
+
+ const bool disableCMakeCallsViaEnvironment =
+ qmlGetConfigOption<bool, qmlConvertBoolConfigOption>("QMLLS_NO_CMAKE_CALLS");
+
+ if (disableCMakeCallsViaEnvironment || parser.isSet(noCMakeCallsOption)) {
+ if (disableCMakeCallsViaEnvironment) {
+ qWarning() << "Disabling CMake calls via QMLLS_NO_CMAKE_CALLS environment variable.";
+ } else {
+ qWarning() << "Disabling CMake calls via command line switch.";
+ }
+
+ qmlServer.codeModel()->disableCMakeCalls();
+ }
+
+ if (parser.isSet(buildDirOption)) {
+ const QStringList dirs =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromOption(parser, buildDirOption);
+
+ qInfo().nospace().noquote()
+ << "Using build directories passed by -b: \"" << dirs.join(u"\", \""_s) << "\".";
+
+ qmlServer.codeModel()->setBuildPathsForRootUrl(QByteArray(), dirs);
+ } else if (QStringList dirsFromEnv =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv("QMLLS_BUILD_DIRS");
+ !dirsFromEnv.isEmpty()) {
+
+ // warn now at qmlls startup that those directories will be used later in qqmlcodemodel when
+ // searching for build folders.
+ qInfo().nospace().noquote() << "Using build directories passed from environment variable "
+ "\"QMLLS_BUILD_DIRS\": \""
+ << dirsFromEnv.join(u"\", \""_s) << "\".";
+
+ } else {
+ qInfo() << "Using the build directories found in the .qmlls.ini file. Your build folder "
+ "might not be found if no .qmlls.ini files are present in the root source "
+ "folder.";
+ }
+ QStringList importPaths{ QLibraryInfo::path(QLibraryInfo::QmlImportsPath) };
+ if (parser.isSet(qmlImportPathOption)) {
+ const QStringList pathsFromOption =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromOption(parser, qmlImportPathOption);
+ qInfo().nospace().noquote() << "Using import directories passed by -I: \""
+ << pathsFromOption.join(u"\", \""_s) << "\".";
+ importPaths << pathsFromOption;
+ }
+ if (parser.isSet(environmentOption)) {
+ if (const QStringList dirsFromEnv =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML_IMPORT_PATH"_s);
+ !dirsFromEnv.isEmpty()) {
+ qInfo().nospace().noquote()
+ << "Using import directories passed from environment variable "
+ "\"QML_IMPORT_PATH\": \""
+ << dirsFromEnv.join(u"\", \""_s) << "\".";
+ importPaths << dirsFromEnv;
+ }
+
+ if (const QStringList dirsFromEnv2 =
+ QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML2_IMPORT_PATH"_s);
+ !dirsFromEnv2.isEmpty()) {
+ qInfo().nospace().noquote()
+ << "Using import directories passed from the deprecated environment variable "
+ "\"QML2_IMPORT_PATH\": \""
+ << dirsFromEnv2.join(u"\", \""_s) << "\".";
+ importPaths << dirsFromEnv2;
+ }
+ }
+ qmlServer.codeModel()->setImportPaths(importPaths);
+
StdinReader r;
+ QThread workerThread;
+ r.moveToThread(&workerThread);
QObject::connect(&r, &StdinReader::receivedData,
qmlServer.server(), &QLanguageServer::receiveData);
- QObject::connect(&r, &StdinReader::eof, &app, [&app]() {
+ QObject::connect(qmlServer.server(), &QLanguageServer::readNextMessage, &r,
+ &StdinReader::readNextMessage);
+ auto exit = [&app, &workerThread]() {
+ workerThread.quit();
+ workerThread.wait();
QTimer::singleShot(100, &app, []() {
QCoreApplication::processEvents();
QCoreApplication::exit();
});
- });
- QThreadPool::globalInstance()->start([&r]() { r.run(); });
+ };
+ QObject::connect(&r, &StdinReader::eof, &app, exit);
+ QObject::connect(qmlServer.server(), &QLanguageServer::shutdown, exit);
+
+ emit r.readNextMessage();
+ workerThread.start();
app.exec();
+ workerThread.quit();
+ workerThread.wait();
return qmlServer.returnValue();
}
diff --git a/tools/qmlls/qmllintsuggestions.cpp b/tools/qmlls/qmllintsuggestions.cpp
deleted file mode 100644
index d08209a054..0000000000
--- a/tools/qmlls/qmllintsuggestions.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "qmllintsuggestions.h"
-#include <QtLanguageServer/private/qlanguageserverspec_p.h>
-#include <QtQmlCompiler/private/qqmljslinter_p.h>
-#include <QtQmlCompiler/private/qqmljslogger_p.h>
-#include <QtCore/qlibraryinfo.h>
-#include <QtCore/qtimer.h>
-#include <QtCore/qdebug.h>
-
-using namespace QLspSpecification;
-using namespace QQmlJS::Dom;
-using namespace Qt::StringLiterals;
-
-Q_LOGGING_CATEGORY(lintLog, "qt.languageserver.lint")
-
-QT_BEGIN_NAMESPACE
-namespace QmlLsp {
-
-static DiagnosticSeverity severityFromMsgType(QtMsgType t)
-{
- switch (t) {
- case QtDebugMsg:
- return DiagnosticSeverity::Hint;
- case QtInfoMsg:
- return DiagnosticSeverity::Information;
- case QtWarningMsg:
- return DiagnosticSeverity::Warning;
- case QtCriticalMsg:
- case QtFatalMsg:
- break;
- }
- return DiagnosticSeverity::Error;
-}
-
-static void codeActionHandler(
- const QByteArray &, const CodeActionParams &params,
- LSPPartialResponse<std::variant<QList<std::variant<Command, CodeAction>>, std::nullptr_t>,
- QList<std::variant<Command, CodeAction>>> &&response)
-{
- QList<std::variant<Command, CodeAction>> responseData;
-
- for (const Diagnostic &diagnostic : params.context.diagnostics) {
- if (!diagnostic.data.has_value())
- continue;
-
- const auto &data = diagnostic.data.value();
-
- int version = data[u"version"].toInt();
- QJsonArray suggestions = data[u"suggestions"].toArray();
-
- QList<TextDocumentEdit> edits;
- QString message;
- for (const QJsonValue &suggestion : suggestions) {
- QString replacement = suggestion[u"replacement"].toString();
- message += suggestion[u"message"].toString() + u"\n";
-
- TextEdit textEdit;
- textEdit.range = { Position { suggestion[u"lspBeginLine"].toInt(),
- suggestion[u"lspBeginCharacter"].toInt() },
- Position { suggestion[u"lspEndLine"].toInt(),
- suggestion[u"lspEndCharacter"].toInt() } };
- textEdit.newText = replacement.toUtf8();
-
- TextDocumentEdit textDocEdit;
- textDocEdit.textDocument = { params.textDocument, version };
- textDocEdit.edits.append(textEdit);
-
- edits.append(textDocEdit);
- }
- message.chop(1);
- WorkspaceEdit edit;
- edit.documentChanges = edits;
-
- CodeAction action;
- action.kind = u"refactor.rewrite"_s.toUtf8();
- action.edit = edit;
- action.title = message.toUtf8();
-
- responseData.append(action);
- }
-
- response.sendResponse(responseData);
-}
-
-void QmlLintSuggestions::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
-{
- protocol->registerCodeActionRequestHandler(&codeActionHandler);
-}
-
-void QmlLintSuggestions::setupCapabilities(const QLspSpecification::InitializeParams &,
- QLspSpecification::InitializeResult &serverInfo)
-{
- serverInfo.capabilities.codeActionProvider = true;
-}
-
-QmlLintSuggestions::QmlLintSuggestions(QLanguageServer *server, QmlLsp::QQmlCodeModel *codeModel)
- : m_server(server), m_codeModel(codeModel)
-{
- QObject::connect(m_codeModel, &QmlLsp::QQmlCodeModel::updatedSnapshot, this,
- &QmlLintSuggestions::diagnose, Qt::DirectConnection);
-}
-
-void QmlLintSuggestions::diagnose(const QByteArray &url)
-{
- const int maxInvalidMsec = 4000;
- qCDebug(lintLog) << "diagnose start";
- QmlLsp::OpenDocumentSnapshot snapshot = m_codeModel->snapshotByUrl(url);
- QList<Diagnostic> diagnostics;
- std::optional<int> version;
- DomItem doc;
- {
- QMutexLocker l(&m_mutex);
- LastLintUpdate &lastUpdate = m_lastUpdate[url];
- if (lastUpdate.version && *lastUpdate.version == version) {
- qCDebug(lspServerLog) << "skipped update of " << url << "unchanged valid doc";
- return;
- }
- if (snapshot.validDocVersion
- && (!lastUpdate.version || *snapshot.validDocVersion > *lastUpdate.version)) {
- doc = snapshot.validDoc;
- version = snapshot.validDocVersion;
- } else if (snapshot.docVersion
- && (!lastUpdate.version || *snapshot.docVersion > *lastUpdate.version)) {
- if (!lastUpdate.version || !snapshot.validDocVersion
- || (lastUpdate.invalidUpdatesSince
- && lastUpdate.invalidUpdatesSince->msecsTo(QDateTime::currentDateTime())
- > maxInvalidMsec)) {
- doc = snapshot.doc;
- version = snapshot.docVersion;
- } else if (!lastUpdate.invalidUpdatesSince) {
- lastUpdate.invalidUpdatesSince = QDateTime::currentDateTime();
- QTimer::singleShot(maxInvalidMsec, Qt::VeryCoarseTimer, this,
- [this, url]() { diagnose(url); });
- }
- }
- if (doc) {
- // update immediately, and do not keep track of sent version, thus in extreme cases sent
- // updates could be out of sync
- lastUpdate.version = version;
- lastUpdate.invalidUpdatesSince.reset();
- }
- }
- QString fileContents;
- if (doc) {
- qCDebug(lintLog) << "has doc, do real lint";
- QStringList imports = m_codeModel->buildPathsForFileUrl(url);
- imports.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
- // add m_server->clientInfo().rootUri & co?
- bool silent = true;
- QString filename = doc.canonicalFilePath();
- fileContents = doc.field(Fields::code).value().toString();
- QStringList qmltypesFiles;
- QStringList resourceFiles;
- QList<QQmlJSLogger::Category> categories;
-
- QQmlJSLinter linter(imports);
-
- linter.lintFile(filename, &fileContents, silent, nullptr, imports, qmltypesFiles,
- resourceFiles, categories);
- auto addLength = [&fileContents](Position &position, int startOffset, int length) {
- int i = startOffset;
- int iEnd = i + length;
- if (iEnd > int(fileContents.size()))
- iEnd = fileContents.size();
- while (i < iEnd) {
- if (fileContents.at(i) == u'\n') {
- ++position.line;
- position.character = 0;
- if (i + 1 < iEnd && fileContents.at(i) == u'\r')
- ++i;
- } else {
- ++position.character;
- }
- ++i;
- }
- };
-
- auto messageToDiagnostic = [&addLength, &version](const Message &message) {
- Diagnostic diagnostic;
- diagnostic.severity = severityFromMsgType(message.type);
- Range &range = diagnostic.range;
- Position &position = range.start;
-
- QQmlJS::SourceLocation srcLoc = message.loc;
-
- position.line = srcLoc.isValid() ? srcLoc.startLine - 1 : 0;
- position.character = srcLoc.isValid() ? srcLoc.startColumn - 1 : 0;
- range.end = position;
- addLength(range.end, srcLoc.isValid() ? message.loc.offset : 0, srcLoc.isValid() ? message.loc.length : 0);
- diagnostic.message = message.message.toUtf8();
- diagnostic.source = QByteArray("qmllint");
-
- auto suggestion = message.fixSuggestion;
- if (suggestion.has_value()) {
- // We need to interject the information about where the fix suggestions end
- // here since we don't have access to the textDocument to calculate it later.
- QJsonArray fixedSuggestions;
- for (const FixSuggestion::Fix &fix : suggestion->fixes) {
- QQmlJS::SourceLocation cut = fix.cutLocation;
-
- int line = cut.isValid() ? cut.startLine - 1 : 0;
- int column = cut.isValid() ? cut.startColumn - 1 : 0;
-
- QJsonObject object;
- object[u"lspBeginLine"] = line;
- object[u"lspBeginCharacter"] = column;
-
- Position end = { line, column };
-
- addLength(end, srcLoc.isValid() ? cut.offset : 0,
- srcLoc.isValid() ? cut.length : 0);
- object[u"lspEndLine"] = end.line;
- object[u"lspEndCharacter"] = end.character;
-
- object[u"message"] = fix.message;
- object[u"replacement"] = fix.replacementString;
-
- fixedSuggestions << object;
- }
- QJsonObject data;
- data[u"suggestions"] = fixedSuggestions;
-
- Q_ASSERT(version.has_value());
- data[u"version"] = version.value();
-
- diagnostic.data = data;
- }
- return diagnostic;
- };
- doc.iterateErrors(
- [&diagnostics, &addLength](DomItem, ErrorMessage msg) {
- Diagnostic diagnostic;
- diagnostic.severity = severityFromMsgType(QtMsgType(int(msg.level)));
- // do something with msg.errorGroups ?
- auto &location = msg.location;
- Range &range = diagnostic.range;
- range.start.line = location.startLine - 1;
- range.start.character = location.startColumn - 1;
- range.end = range.start;
- addLength(range.end, location.offset, location.length);
- diagnostic.code = QByteArray(msg.errorId.data(), msg.errorId.size());
- diagnostic.source = "domParsing";
- diagnostic.message = msg.message.toUtf8();
- diagnostics.append(diagnostic);
- return true;
- },
- true);
-
- if (const QQmlJSLogger *logger = linter.logger()) {
- qsizetype nDiagnostics = diagnostics.size();
- for (const auto &messages : { logger->infos(), logger->warnings(), logger->errors() }) {
- for (const Message &message : messages) {
- diagnostics.append(messageToDiagnostic(message));
- }
- }
- if (diagnostics.size() != nDiagnostics && imports.size() == 1) {
- Diagnostic diagnostic;
- diagnostic.severity = DiagnosticSeverity::Warning;
- Range &range = diagnostic.range;
- Position &position = range.start;
- position.line = 0;
- position.character = 0;
- Position &positionEnd = range.end;
- positionEnd.line = 1;
- diagnostic.message =
- "qmlls could not find a build directory, without a build directory "
- "containing a current build there could be spurious warnings, you might "
- "want to pass the --build-dir <buildDir> option to qmlls, or set the "
- "environment variable QMLLS_BUILD_DIRS.";
- diagnostic.source = QByteArray("qmllint");
- diagnostics.append(diagnostic);
- }
- }
- }
- PublishDiagnosticsParams diagnosticParams;
- diagnosticParams.uri = url;
- diagnosticParams.diagnostics = diagnostics;
- diagnosticParams.version = version;
-
- m_server->protocol()->notifyPublishDiagnostics(diagnosticParams);
- qCDebug(lintLog) << "lint" << QString::fromUtf8(url) << "found"
- << diagnosticParams.diagnostics.size() << "issues"
- << QTypedJson::toJsonValue(diagnosticParams);
-}
-
-} // namespace QmlLsp
-QT_END_NAMESPACE
diff --git a/tools/qmlls/qmllintsuggestions.h b/tools/qmlls/qmllintsuggestions.h
deleted file mode 100644
index 9d738a269f..0000000000
--- a/tools/qmlls/qmllintsuggestions.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef QMLLINTSUGGESTIONS_H
-#define QMLLINTSUGGESTIONS_H
-
-#include "qlanguageserver.h"
-#include "qqmlcodemodel.h"
-
-#include <optional>
-
-QT_BEGIN_NAMESPACE
-namespace QmlLsp {
-struct LastLintUpdate
-{
- std::optional<int> version;
- std::optional<QDateTime> invalidUpdatesSince;
-};
-
-class QmlLintSuggestions : public QLanguageServerModule
-{
- Q_OBJECT
-public:
- QmlLintSuggestions(QLanguageServer *server, QmlLsp::QQmlCodeModel *codeModel);
-
- QString name() const override { return QLatin1StringView("QmlLint Suggestions"); }
-public Q_SLOTS:
- void diagnose(const QByteArray &uri);
- void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
- void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &) override;
-
-private:
- QMutex m_mutex;
- QHash<QByteArray, LastLintUpdate> m_lastUpdate;
- QLanguageServer *m_server;
- QmlLsp::QQmlCodeModel *m_codeModel;
-};
-} // namespace QmlLsp
-QT_END_NAMESPACE
-#endif // QMLLINTSUGGESTIONS_H
diff --git a/tools/qmlls/qqmlcodemodel.cpp b/tools/qmlls/qqmlcodemodel.cpp
deleted file mode 100644
index 9c3521aa57..0000000000
--- a/tools/qmlls/qqmlcodemodel.cpp
+++ /dev/null
@@ -1,700 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "qqmllanguageserver.h"
-#include "qqmlcodemodel.h"
-#include <QtCore/qfileinfo.h>
-#include <QtCore/qdir.h>
-#include <QtCore/qthreadpool.h>
-#include <QtCore/qlibraryinfo.h>
-#include <QtQmlDom/private/qqmldomtop_p.h>
-#include "textdocument.h"
-
-#include <memory>
-#include <algorithm>
-
-QT_BEGIN_NAMESPACE
-
-namespace QmlLsp {
-
-Q_LOGGING_CATEGORY(codeModelLog, "qt.languageserver.codemodel")
-
-using namespace QQmlJS::Dom;
-using namespace Qt::StringLiterals;
-
-/*!
-\internal
-\class QQmlCodeModel
-
-The code model offers a view of the current state of the current files, and traks open files.
-All methods are threadsafe, and generally return immutable or threadsafe objects that can be
-worked on from any thread (unless otherwise noted).
-The idea is the let all other operations be as lock free as possible, concentrating all tricky
-synchronization here.
-
-\section2 Global views
-\list
-\li currentEnv() offers a view that contains the latest version of all the loaded files
-\li validEnv() is just like current env but stores only the valid (meaning correctly parsed,
- not necessarily without errors) version of a file, it is normally a better choice to load the
- dependencies/symbol information from
-\endlist
-
-\section2 OpenFiles
-\list
-\li snapshotByUrl() returns an OpenDocumentSnapshot of an open document. From it you can get the
- document, its latest valid version, scope, all connected to a specific version of the document
- and immutable. The signal updatedSnapshot() is called every time a snapshot changes (also for
- every partial change: document change, validDocument change, scope change).
-\li openDocumentByUrl() is a lower level and more intrusive access to OpenDocument objects. These
- contains the current snapshot, and shared pointer to a Utils::TextDocument. This is *always* the
- current version of the document, and has line by line support.
- Working on it is more delicate and intrusive, because you have to explicitly acquire its mutex()
- before *any* read or write/modification to it.
- It has a version nuber which is supposed to always change and increase.
- It is mainly used for highlighting/indenting, and is immediately updated when the user edits a
- document. Its use should be avoided if possible, preferring the snapshots.
-\endlist
-
-\section2 Parallelism/Theading
-Most operations are not parallel and usually take place in the main thread (but are still thread
-safe).
-There are two main task that are executed in parallel: Indexing, and OpenDocumentUpdate.
-Indexing is meant to keep the global view up to date.
-OpenDocumentUpdate keeps the snapshots of the open documents up to date.
-
-There is always a tension between being responsive, using all threads available, and avoid to hog
-too many resources. One can choose different parallelization strategies, we went with a flexiable
-approach.
-We have (private) functions that execute part of the work: indexSome() and openUpdateSome(). These
-do all locking needed, get some work, do it without locks, and at the end update the state of the
-code model. If there is more work, then they return true. Thus while (xxxSome()); works until there
-is no work left.
-
-addDirectoriesToIndex(), the internal addDirectory() and addOpenToUpdate() add more work to do.
-
-indexNeedsUpdate() and openNeedUpdate(), check if there is work to do, and if yes ensure that a
-worker thread (or more) that work on it exist.
-*/
-
-QQmlCodeModel::QQmlCodeModel(QObject *parent, QQmlToolingSettings *settings)
- : QObject { parent },
- m_currentEnv(std::make_shared<DomEnvironment>(
- QStringList(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)),
- DomEnvironment::Option::SingleThreaded)),
- m_validEnv(std::make_shared<DomEnvironment>(
- QStringList(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)),
- DomEnvironment::Option::SingleThreaded)),
- m_settings(settings)
-{
-}
-
-QQmlCodeModel::~QQmlCodeModel()
-{
- while (true) {
- bool shouldWait;
- {
- QMutexLocker l(&m_mutex);
- m_state = State::Stopping;
- m_openDocumentsToUpdate.clear();
- shouldWait = m_nIndexInProgress != 0 || m_nUpdateInProgress != 0;
- }
- if (!shouldWait)
- break;
- QThread::yieldCurrentThread();
- }
-}
-
-OpenDocumentSnapshot QQmlCodeModel::snapshotByUrl(const QByteArray &url)
-{
- return openDocumentByUrl(url).snapshot;
-}
-
-int QQmlCodeModel::indexEvalProgress() const
-{
- Q_ASSERT(!m_mutex.tryLock()); // should be called while locked
- const int dirCost = 10;
- int costToDo = 1;
- for (const ToIndex &el : std::as_const(m_toIndex))
- costToDo += dirCost * el.leftDepth;
- costToDo += m_indexInProgressCost;
- return m_indexDoneCost * 100 / (costToDo + m_indexDoneCost);
-}
-
-void QQmlCodeModel::indexStart()
-{
- Q_ASSERT(!m_mutex.tryLock()); // should be called while locked
- qCDebug(codeModelLog) << "indexStart";
-}
-
-void QQmlCodeModel::indexEnd()
-{
- Q_ASSERT(!m_mutex.tryLock()); // should be called while locked
- qCDebug(codeModelLog) << "indexEnd";
- m_lastIndexProgress = 0;
- m_nIndexInProgress = 0;
- m_toIndex.clear();
- m_indexInProgressCost = 0;
- m_indexDoneCost = 0;
-}
-
-void QQmlCodeModel::indexSendProgress(int progress)
-{
- if (progress <= m_lastIndexProgress)
- return;
- m_lastIndexProgress = progress;
- // ### actually send progress
-}
-
-bool QQmlCodeModel::indexCancelled()
-{
- QMutexLocker l(&m_mutex);
- if (m_state == State::Stopping)
- return true;
- return false;
-}
-
-void QQmlCodeModel::indexDirectory(const QString &path, int depthLeft)
-{
- if (indexCancelled())
- return;
- QDir dir(path);
- if (depthLeft > 1) {
- const QStringList dirs =
- dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
- for (const QString &child : dirs)
- addDirectory(dir.filePath(child), --depthLeft);
- }
- const QStringList qmljs = dir.entryList(QStringList({ "*.qml", "*.js", "*.mjs" }), QDir::Files);
- int progress = 0;
- {
- QMutexLocker l(&m_mutex);
- m_indexInProgressCost += qmljs.size();
- progress = indexEvalProgress();
- }
- indexSendProgress(progress);
- if (qmljs.isEmpty())
- return;
- DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
- for (const QString &file : qmljs) {
- if (indexCancelled())
- return;
- QString fPath = dir.filePath(file);
- QFileInfo fInfo(fPath);
- QString cPath = fInfo.canonicalFilePath();
- if (!cPath.isEmpty()) {
- newCurrent.loadBuiltins();
- newCurrent.loadFile(cPath, fPath, [](Path, DomItem &, DomItem &) {}, {});
- newCurrent.loadPendingDependencies();
- newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
- }
- {
- QMutexLocker l(&m_mutex);
- ++m_indexDoneCost;
- --m_indexInProgressCost;
- progress = indexEvalProgress();
- }
- indexSendProgress(progress);
- }
-}
-
-void QQmlCodeModel::addDirectoriesToIndex(const QStringList &paths, QLanguageServer *server)
-{
- Q_UNUSED(server);
- // ### create progress, &scan in a separate instance
- const int maxDepth = 5;
- for (const auto &path : paths)
- addDirectory(path, maxDepth);
- indexNeedsUpdate();
-}
-
-void QQmlCodeModel::addDirectory(const QString &path, int depthLeft)
-{
- if (depthLeft < 1)
- return;
- {
- QMutexLocker l(&m_mutex);
- for (auto it = m_toIndex.begin(); it != m_toIndex.end();) {
- if (it->path.startsWith(path)) {
- if (it->path.size() == path.size())
- return;
- if (it->path.at(path.size()) == u'/') {
- it = m_toIndex.erase(it);
- continue;
- }
- } else if (path.startsWith(it->path) && path.at(it->path.size()) == u'/')
- return;
- ++it;
- }
- m_toIndex.append({ path, depthLeft });
- }
-}
-
-void QQmlCodeModel::removeDirectory(const QString &path)
-{
- {
- QMutexLocker l(&m_mutex);
- auto toRemove = [path](const QString &p) {
- return p.startsWith(path) && (p.size() == path.size() || p.at(path.size()) == u'/');
- };
- auto it = m_toIndex.begin();
- auto end = m_toIndex.end();
- while (it != end) {
- if (toRemove(it->path))
- it = m_toIndex.erase(it);
- else
- ++it;
- }
- }
- if (auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
- validEnvPtr->removePath(path);
- if (auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
- currentEnvPtr->removePath(path);
-}
-
-QString QQmlCodeModel::url2Path(const QByteArray &url, UrlLookup options)
-{
- QString res;
- {
- QMutexLocker l(&m_mutex);
- res = m_url2path.value(url);
- }
- if (!res.isEmpty() && options == UrlLookup::Caching)
- return res;
- QUrl qurl(QString::fromUtf8(url));
- QFileInfo f(qurl.toLocalFile());
- QString cPath = f.canonicalFilePath();
- if (cPath.isEmpty())
- cPath = f.filePath();
- {
- QMutexLocker l(&m_mutex);
- if (!res.isEmpty() && res != cPath)
- m_path2url.remove(res);
- m_url2path.insert(url, cPath);
- m_path2url.insert(cPath, url);
- }
- return cPath;
-}
-
-void QQmlCodeModel::newOpenFile(const QByteArray &url, int version, const QString &docText)
-{
- {
- QMutexLocker l(&m_mutex);
- auto &openDoc = m_openDocuments[url];
- if (!openDoc.textDocument)
- openDoc.textDocument = std::make_shared<Utils::TextDocument>();
- QMutexLocker l2(openDoc.textDocument->mutex());
- openDoc.textDocument->setVersion(version);
- openDoc.textDocument->setPlainText(docText);
- }
- addOpenToUpdate(url);
- openNeedUpdate();
-}
-
-OpenDocument QQmlCodeModel::openDocumentByUrl(const QByteArray &url)
-{
- QMutexLocker l(&m_mutex);
- return m_openDocuments.value(url);
-}
-
-void QQmlCodeModel::indexNeedsUpdate()
-{
- const int maxIndexThreads = 1;
- {
- QMutexLocker l(&m_mutex);
- if (m_toIndex.isEmpty() || m_nIndexInProgress >= maxIndexThreads)
- return;
- if (++m_nIndexInProgress == 1)
- indexStart();
- }
- QThreadPool::globalInstance()->start([this]() {
- while (indexSome()) { }
- });
-}
-
-bool QQmlCodeModel::indexSome()
-{
- qCDebug(codeModelLog) << "indexSome";
- ToIndex toIndex;
- {
- QMutexLocker l(&m_mutex);
- if (m_toIndex.isEmpty()) {
- if (--m_nIndexInProgress == 0)
- indexEnd();
- return false;
- }
- toIndex = m_toIndex.last();
- m_toIndex.removeLast();
- }
- bool hasMore = false;
- {
- auto guard = qScopeGuard([this, &hasMore]() {
- QMutexLocker l(&m_mutex);
- if (m_toIndex.isEmpty()) {
- if (--m_nIndexInProgress == 0)
- indexEnd();
- hasMore = false;
- } else {
- hasMore = true;
- }
- });
- indexDirectory(toIndex.path, toIndex.leftDepth);
- }
- return hasMore;
-}
-
-void QQmlCodeModel::openNeedUpdate()
-{
- qCDebug(codeModelLog) << "openNeedUpdate";
- const int maxIndexThreads = 1;
- {
- QMutexLocker l(&m_mutex);
- if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxIndexThreads)
- return;
- if (++m_nUpdateInProgress == 1)
- openUpdateStart();
- }
- QThreadPool::globalInstance()->start([this]() {
- while (openUpdateSome()) { }
- });
-}
-
-bool QQmlCodeModel::openUpdateSome()
-{
- qCDebug(codeModelLog) << "openUpdateSome start";
- QByteArray toUpdate;
- {
- QMutexLocker l(&m_mutex);
- if (m_openDocumentsToUpdate.isEmpty()) {
- if (--m_nUpdateInProgress == 0)
- openUpdateEnd();
- return false;
- }
- auto it = m_openDocumentsToUpdate.find(m_lastOpenDocumentUpdated);
- auto end = m_openDocumentsToUpdate.end();
- if (it == end)
- it = m_openDocumentsToUpdate.begin();
- else if (++it == end)
- it = m_openDocumentsToUpdate.begin();
- toUpdate = *it;
- m_openDocumentsToUpdate.erase(it);
- }
- bool hasMore = false;
- {
- auto guard = qScopeGuard([this, &hasMore]() {
- QMutexLocker l(&m_mutex);
- if (m_openDocumentsToUpdate.isEmpty()) {
- if (--m_nUpdateInProgress == 0)
- openUpdateEnd();
- hasMore = false;
- } else {
- hasMore = true;
- }
- });
- openUpdate(toUpdate);
- }
- return hasMore;
-}
-
-void QQmlCodeModel::openUpdateStart()
-{
- qCDebug(codeModelLog) << "openUpdateStart";
-}
-
-void QQmlCodeModel::openUpdateEnd()
-{
- qCDebug(codeModelLog) << "openUpdateEnd";
-}
-
-void QQmlCodeModel::newDocForOpenFile(const QByteArray &url, int version, const QString &docText)
-{
- qCDebug(codeModelLog) << "updating doc" << url << "to version" << version << "("
- << docText.size() << "chars)";
- DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
- QStringList loadPaths = buildPathsForFileUrl(url);
- loadPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
- if (std::shared_ptr<DomEnvironment> newCurrentPtr = newCurrent.ownerAs<DomEnvironment>()) {
- newCurrentPtr->setLoadPaths(loadPaths);
- }
- QString fPath = url2Path(url, UrlLookup::ForceLookup);
- Path p;
- newCurrent.loadFile(
- fPath, fPath, docText, QDateTime::currentDateTimeUtc(),
- [&p](Path, DomItem &, DomItem &newValue) { p = newValue.fileObject().canonicalPath(); },
- {});
- newCurrent.loadPendingDependencies();
- if (p) {
- newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
- DomItem item = m_currentEnv.path(p);
- {
- QMutexLocker l(&m_mutex);
- OpenDocument &doc = m_openDocuments[url];
- if (!doc.textDocument) {
- qCWarning(lspServerLog)
- << "ignoring update to closed document" << QString::fromUtf8(url);
- return;
- } else {
- QMutexLocker l(doc.textDocument->mutex());
- if (doc.textDocument->version() && *doc.textDocument->version() > version) {
- qCWarning(lspServerLog)
- << "docUpdate: version" << version << "of document"
- << QString::fromUtf8(url) << "is not the latest anymore";
- return;
- }
- }
- if (!doc.snapshot.docVersion || *doc.snapshot.docVersion < version) {
- doc.snapshot.docVersion = version;
- doc.snapshot.doc = item;
- } else {
- qCWarning(lspServerLog) << "skipping update of current doc to obsolete version"
- << version << "of document" << QString::fromUtf8(url);
- }
- if (item.field(Fields::isValid).value().toBool(false)) {
- if (!doc.snapshot.validDocVersion || *doc.snapshot.validDocVersion < version) {
- DomItem vDoc = m_validEnv.path(p);
- doc.snapshot.validDocVersion = version;
- doc.snapshot.validDoc = vDoc;
- } else {
- qCWarning(lspServerLog) << "skippig update of valid doc to obsolete version"
- << version << "of document" << QString::fromUtf8(url);
- }
- } else {
- qCWarning(lspServerLog)
- << "avoid update of validDoc to " << version << "of document"
- << QString::fromUtf8(url) << "as it is invalid";
- }
- }
- }
- if (codeModelLog().isDebugEnabled()) {
- qCDebug(codeModelLog) << "finished update doc of " << url << "to version" << version;
- snapshotByUrl(url).dump(qDebug() << "postSnapshot",
- OpenDocumentSnapshot::DumpOption::AllCode);
- }
- // we should update the scope in the future thus call addOpen(url)
- emit updatedSnapshot(url);
-}
-
-void QQmlCodeModel::closeOpenFile(const QByteArray &url)
-{
- QMutexLocker l(&m_mutex);
- m_openDocuments.remove(url);
-}
-
-void QQmlCodeModel::setRootUrls(const QList<QByteArray> &urls)
-{
- QMutexLocker l(&m_mutex);
- m_rootUrls = urls;
-}
-
-void QQmlCodeModel::addRootUrls(const QList<QByteArray> &urls)
-{
- QMutexLocker l(&m_mutex);
- for (const QByteArray &url : urls) {
- if (!m_rootUrls.contains(url))
- m_rootUrls.append(url);
- }
-}
-
-void QQmlCodeModel::removeRootUrls(const QList<QByteArray> &urls)
-{
- QMutexLocker l(&m_mutex);
- for (const QByteArray &url : urls)
- m_rootUrls.removeOne(url);
-}
-
-QList<QByteArray> QQmlCodeModel::rootUrls() const
-{
- QMutexLocker l(&m_mutex);
- return m_rootUrls;
-}
-
-QStringList QQmlCodeModel::buildPathsForRootUrl(const QByteArray &url)
-{
- QMutexLocker l(&m_mutex);
- return m_buildPathsForRootUrl.value(url);
-}
-
-static bool isNotSeparator(char c)
-{
- return c != '/';
-}
-
-QStringList QQmlCodeModel::buildPathsForFileUrl(const QByteArray &url)
-{
- QList<QByteArray> roots;
- {
- QMutexLocker l(&m_mutex);
- roots = m_buildPathsForRootUrl.keys();
- }
- // we want to longest match to be first, as it should override shorter matches
- std::sort(roots.begin(), roots.end(), [](const QByteArray &el1, const QByteArray &el2) {
- if (el1.size() > el2.size())
- return true;
- if (el1.size() < el2.size())
- return false;
- return el1 < el2;
- });
- QStringList buildPaths;
- QStringList defaultValues;
- if (!roots.isEmpty() && roots.last().isEmpty())
- roots.removeLast();
- QByteArray urlSlash(url);
- if (!urlSlash.isEmpty() && isNotSeparator(urlSlash.at(urlSlash.size() - 1)))
- urlSlash.append('/');
- // look if the file has a know prefix path
- for (const QByteArray &root : roots) {
- if (urlSlash.startsWith(root)) {
- buildPaths += buildPathsForRootUrl(root);
- break;
- }
- }
- QString path = url2Path(url);
- if (buildPaths.isEmpty() && m_settings) {
- // look in the settings
- m_settings->search(path);
- QString buildDir = QStringLiteral(u"buildDir");
- if (m_settings->isSet(buildDir))
- buildPaths += m_settings->value(buildDir).toString().split(',', Qt::SkipEmptyParts);
- }
- if (buildPaths.isEmpty()) {
- // default values
- buildPaths += buildPathsForRootUrl(QByteArray());
- }
- // env variable
- QStringList envPaths = qEnvironmentVariable("QMLLS_BUILD_DIRS").split(',', Qt::SkipEmptyParts);
- buildPaths += envPaths;
- if (buildPaths.isEmpty()) {
- // heuristic to find build dir
- QDir d(path);
- d.setNameFilters(QStringList({ u"build*"_s }));
- const int maxDirDepth = 8;
- int iDir = maxDirDepth;
- QString dirName = d.dirName();
- QDateTime lastModified;
- while (d.cdUp() && --iDir > 0) {
- for (const QFileInfo &fInfo : d.entryInfoList(QDir::Dirs)) {
- if (fInfo.completeBaseName() == u"build"
- || fInfo.completeBaseName().startsWith(u"build-%1"_s.arg(dirName))) {
- if (iDir > 1)
- iDir = 1;
- if (!lastModified.isValid() || lastModified < fInfo.lastModified()) {
- buildPaths.clear();
- buildPaths.append(fInfo.absoluteFilePath());
- }
- }
- }
- }
- }
- // add dependent build directories
- QStringList res;
- std::reverse(buildPaths.begin(), buildPaths.end());
- const int maxDeps = 4;
- while (!buildPaths.isEmpty()) {
- QString bPath = buildPaths.last();
- buildPaths.removeLast();
- res += bPath;
- if (QFile::exists(bPath + u"/_deps") && bPath.split(u"/_deps/"_s).size() < maxDeps) {
- QDir d(bPath + u"/_deps");
- for (const QFileInfo &fInfo : d.entryInfoList(QDir::Dirs))
- buildPaths.append(fInfo.absoluteFilePath());
- }
- }
- return res;
-}
-
-void QQmlCodeModel::setBuildPathsForRootUrl(QByteArray url, const QStringList &paths)
-{
- QMutexLocker l(&m_mutex);
- if (!url.isEmpty() && isNotSeparator(url.at(url.size() - 1)))
- url.append('/');
- if (paths.isEmpty())
- m_buildPathsForRootUrl.remove(url);
- else
- m_buildPathsForRootUrl.insert(url, paths);
-}
-
-void QQmlCodeModel::openUpdate(const QByteArray &url)
-{
- bool updateDoc = false;
- bool updateScope = false;
- std::optional<int> rNow = 0;
- QString docText;
- DomItem validDoc;
- std::shared_ptr<Utils::TextDocument> document;
- {
- QMutexLocker l(&m_mutex);
- OpenDocument &doc = m_openDocuments[url];
- document = doc.textDocument;
- if (!document)
- return;
- {
- QMutexLocker l2(document->mutex());
- rNow = document->version();
- }
- if (rNow && (!doc.snapshot.docVersion || *doc.snapshot.docVersion != *rNow))
- updateDoc = true;
- else if (doc.snapshot.validDocVersion
- && (!doc.snapshot.scopeVersion
- || *doc.snapshot.scopeVersion != *doc.snapshot.validDocVersion))
- updateScope = true;
- else
- return;
- if (updateDoc) {
- QMutexLocker l2(doc.textDocument->mutex());
- rNow = doc.textDocument->version();
- docText = doc.textDocument->toPlainText();
- } else {
- validDoc = doc.snapshot.validDoc;
- rNow = doc.snapshot.validDocVersion;
- }
- }
- if (updateDoc) {
- newDocForOpenFile(url, *rNow, docText);
- }
- if (updateScope) {
- // to do
- }
-}
-
-void QQmlCodeModel::addOpenToUpdate(const QByteArray &url)
-{
- QMutexLocker l(&m_mutex);
- m_openDocumentsToUpdate.insert(url);
-}
-
-QDebug OpenDocumentSnapshot::dump(QDebug dbg, DumpOptions options)
-{
- dbg.noquote().nospace() << "{";
- dbg << " url:" << QString::fromUtf8(url) << "\n";
- dbg << " docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) << "\n";
- if (options & DumpOption::LatestCode) {
- dbg << " doc: ------------\n"
- << doc.field(Fields::code).value().toString() << "\n==========\n";
- } else {
- dbg << u" doc:"
- << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
- : u"*none*"_s)
- << "\n";
- }
- dbg << " validDocVersion:"
- << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) << "\n";
- if (options & DumpOption::ValidCode) {
- dbg << " validDoc: ------------\n"
- << validDoc.field(Fields::code).value().toString() << "\n==========\n";
- } else {
- dbg << u" validDoc:"
- << (validDoc ? u"%1chars"_s.arg(
- validDoc.field(Fields::code).value().toString().size())
- : u"*none*"_s)
- << "\n";
- }
- dbg << " scopeVersion:" << (scopeVersion ? QString::number(*scopeVersion) : u"*none*"_s)
- << "\n";
- dbg << " scopeDependenciesLoadTime:" << scopeDependenciesLoadTime << "\n";
- dbg << " scopeDependenciesChanged" << scopeDependenciesChanged << "\n";
- dbg << "}";
- return dbg;
-}
-
-} // namespace QmlLsp
-
-QT_END_NAMESPACE
diff --git a/tools/qmlls/qqmlcodemodel.h b/tools/qmlls/qqmlcodemodel.h
deleted file mode 100644
index 549f7dd1a1..0000000000
--- a/tools/qmlls/qqmlcodemodel.h
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef QQMLCODEMODEL_H
-#define QQMLCODEMODEL_H
-
-#include <QObject>
-#include <QHash>
-#include <QtQmlDom/private/qqmldomitem_p.h>
-#include <QtQmlCompiler/private/qqmljsscope_p.h>
-#include "qlanguageserver_p.h"
-#include "textdocument.h"
-#include "../shared/qqmltoolingsettings.h"
-
-#include <functional>
-#include <memory>
-
-QT_BEGIN_NAMESPACE
-class TextSynchronization;
-namespace QmlLsp {
-
-class OpenDocumentSnapshot
-{
-public:
- enum class DumpOption {
- NoCode = 0,
- LatestCode = 0x1,
- ValidCode = 0x2,
- AllCode = LatestCode | ValidCode
- };
- Q_DECLARE_FLAGS(DumpOptions, DumpOption)
- QStringList searchPath;
- QByteArray url;
- std::optional<int> docVersion;
- QQmlJS::Dom::DomItem doc;
- std::optional<int> validDocVersion;
- QQmlJS::Dom::DomItem validDoc;
- std::optional<int> scopeVersion;
- QDateTime scopeDependenciesLoadTime;
- bool scopeDependenciesChanged = false;
- QQmlJSScope::Ptr scope = {};
- QDebug dump(QDebug dbg, DumpOptions dump = DumpOption::NoCode);
-};
-
-Q_DECLARE_OPERATORS_FOR_FLAGS(OpenDocumentSnapshot::DumpOptions)
-
-class OpenDocument
-{
-public:
- OpenDocumentSnapshot snapshot;
- std::shared_ptr<Utils::TextDocument> textDocument;
-};
-
-struct ToIndex
-{
- QString path;
- int leftDepth;
-};
-
-class QQmlCodeModel : public QObject
-{
- Q_OBJECT
-public:
- enum class UrlLookup { Caching, ForceLookup };
- enum class State { Running, Stopping };
-
- explicit QQmlCodeModel(QObject *parent = nullptr, QQmlToolingSettings *settings = nullptr);
- ~QQmlCodeModel();
- QQmlJS::Dom::DomItem currentEnv();
- QQmlJS::Dom::DomItem validEnv();
- OpenDocumentSnapshot snapshotByUrl(const QByteArray &url);
- OpenDocument openDocumentByUrl(const QByteArray &url);
-
- void openNeedUpdate();
- void indexNeedsUpdate();
- void addDirectoriesToIndex(const QStringList &paths, QLanguageServer *server);
- void addOpenToUpdate(const QByteArray &);
- void removeDirectory(const QString &path);
- // void updateDocument(const OpenDocument &doc);
- QString url2Path(const QByteArray &url, UrlLookup options = UrlLookup::Caching);
- void newOpenFile(const QByteArray &url, int version, const QString &docText);
- void newDocForOpenFile(const QByteArray &url, int version, const QString &docText);
- void closeOpenFile(const QByteArray &url);
- void setRootUrls(const QList<QByteArray> &urls);
- QList<QByteArray> rootUrls() const;
- void addRootUrls(const QList<QByteArray> &urls);
- QStringList buildPathsForRootUrl(const QByteArray &url);
- QStringList buildPathsForFileUrl(const QByteArray &url);
- void setBuildPathsForRootUrl(QByteArray url, const QStringList &paths);
- void removeRootUrls(const QList<QByteArray> &urls);
- QQmlToolingSettings *settings();
-Q_SIGNALS:
- void updatedSnapshot(const QByteArray &url);
-private:
- void indexDirectory(const QString &path, int depthLeft);
- int indexEvalProgress() const; // to be called in the mutex
- void indexStart(); // to be called in the mutex
- void indexEnd(); // to be called in the mutex
- void indexSendProgress(int progress);
- bool indexCancelled();
- bool indexSome();
- void addDirectory(const QString &path, int leftDepth);
- bool openUpdateSome();
- void openUpdateStart();
- void openUpdateEnd();
- void openUpdate(const QByteArray &);
- mutable QMutex m_mutex;
- State m_state = State::Running;
- int m_lastIndexProgress = 0;
- int m_nIndexInProgress = 0;
- QList<ToIndex> m_toIndex;
- int m_indexInProgressCost = 0;
- int m_indexDoneCost = 0;
- int m_nUpdateInProgress = 0;
- QQmlJS::Dom::DomItem m_currentEnv;
- QQmlJS::Dom::DomItem m_validEnv;
- QByteArray m_lastOpenDocumentUpdated;
- QSet<QByteArray> m_openDocumentsToUpdate;
- QHash<QByteArray, QStringList> m_buildPathsForRootUrl;
- QList<QByteArray> m_rootUrls;
- QHash<QByteArray, QString> m_url2path;
- QHash<QString, QByteArray> m_path2url;
- QHash<QByteArray, OpenDocument> m_openDocuments;
- QQmlToolingSettings *m_settings;
-};
-
-} // namespace QmlLsp
-QT_END_NAMESPACE
-#endif // QQMLCODEMODEL_H
diff --git a/tools/qmlls/qqmllanguageserver.cpp b/tools/qmlls/qqmllanguageserver.cpp
deleted file mode 100644
index 48639f37d2..0000000000
--- a/tools/qmlls/qqmllanguageserver.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "qqmllanguageserver.h"
-
-#include "textsynchronization.h"
-
-#include "qlanguageserver.h"
-#include "lspcustomtypes.h"
-#include <QtCore/qdir.h>
-
-#include <iostream>
-#include <algorithm>
-
-QT_BEGIN_NAMESPACE
-
-namespace QmlLsp {
-
-using namespace QLspSpecification;
-using namespace Qt::StringLiterals;
-/*!
-\internal
-\class QmlLsp::QQmlLanguageServer
-\brief Class that sets up a QmlLanguageServer
-
-This class sets up a QML language server.
-It needs a function
-\code
-std::function<void(const QByteArray &)> sendData
-\endcode
-to send out its replies, and one should feed the data it receives to the server()->receive() method.
-It is expected to call this method only from a single thread, and not to block, the simplest way to
-achieve this is to avoid direct calls, and connect it as slot, while reading from another thread.
-
-The Server is build with separate QLanguageServerModule that implement a given functionality, and
-all of them are constructed and registered with the QLanguageServer in the constructor o this class.
-
-Generally all operations are expected to be done in the object thread, and handlers are always
-called from it, but they are free to delegate the response to another thread, the response handler
-is thread safe. All the methods of the server() obect are also threadsafe.
-
-The code model starts other threads to update its state, see its documentation for more information.
-*/
-QQmlLanguageServer::QQmlLanguageServer(std::function<void(const QByteArray &)> sendData,
- QQmlToolingSettings *settings)
- : m_codeModel(nullptr, settings),
- m_server(sendData),
- m_textSynchronization(&m_codeModel),
- m_lint(&m_server, &m_codeModel),
- m_workspace(&m_codeModel),
- m_completionSupport(&m_codeModel)
-{
- m_server.addServerModule(this);
- m_server.addServerModule(&m_textSynchronization);
- m_server.addServerModule(&m_lint);
- m_server.addServerModule(&m_workspace);
- m_server.addServerModule(&m_completionSupport);
- m_server.finishSetup();
- qCWarning(lspServerLog) << "Did Setup";
-}
-
-void QQmlLanguageServer::registerHandlers(QLanguageServer *server,
- QLanguageServerProtocol *protocol)
-{
- Q_UNUSED(protocol);
- QObject::connect(server, &QLanguageServer::lifecycleError, this,
- &QQmlLanguageServer::errorExit);
- QObject::connect(server, &QLanguageServer::exit, this, &QQmlLanguageServer::exit);
- QObject::connect(server, &QLanguageServer::runStatusChanged, [](QLanguageServer::RunStatus r) {
- qCDebug(lspServerLog) << "runStatus" << int(r);
- });
- protocol->typedRpc()->registerNotificationHandler<Notifications::AddBuildDirsParams>(
- QByteArray(Notifications::AddBuildDirsMethod),
- [this](const QByteArray &, const Notifications::AddBuildDirsParams &params) {
- for (const auto &buildDirs : params.buildDirsToSet) {
- QStringList dirPaths;
- dirPaths.resize(buildDirs.buildDirs.size());
- std::transform(buildDirs.buildDirs.begin(), buildDirs.buildDirs.end(),
- dirPaths.begin(), [](const QByteArray &utf8Str) {
- return QString::fromUtf8(utf8Str);
- });
- m_codeModel.setBuildPathsForRootUrl(buildDirs.baseUri, dirPaths);
- }
- });
-}
-
-void QQmlLanguageServer::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &serverInfo)
-{
- Q_UNUSED(clientInfo);
- QJsonObject expCap;
- if (serverInfo.capabilities.experimental.has_value() && serverInfo.capabilities.experimental->isObject())
- expCap = serverInfo.capabilities.experimental->toObject();
- expCap.insert(u"addBuildDirs"_s, QJsonObject({ { u"supported"_s, true } }));
- serverInfo.capabilities.experimental = expCap;
-}
-
-QString QQmlLanguageServer::name() const
-{
- return u"QQmlLanguageServer"_s;
-}
-
-void QQmlLanguageServer::errorExit()
-{
- qCWarning(lspServerLog) << "Error exit";
- fclose(stdin);
-}
-
-void QQmlLanguageServer::exit()
-{
- m_returnValue = 0;
- fclose(stdin);
-}
-
-int QQmlLanguageServer::returnValue() const
-{
- return m_returnValue;
-}
-
-QQmlCodeModel *QQmlLanguageServer::codeModel()
-{
- return &m_codeModel;
-}
-
-QLanguageServer *QQmlLanguageServer::server()
-{
- return &m_server;
-}
-
-TextSynchronization *QQmlLanguageServer::textSynchronization()
-{
- return &m_textSynchronization;
-}
-
-QmlLintSuggestions *QQmlLanguageServer::lint()
-{
- return &m_lint;
-}
-
-WorkspaceHandlers *QQmlLanguageServer::worspace()
-{
- return &m_workspace;
-}
-
-} // namespace QmlLsp
-
-QT_END_NAMESPACE
diff --git a/tools/qmlls/qqmllanguageserver.h b/tools/qmlls/qqmllanguageserver.h
deleted file mode 100644
index 7efe93990a..0000000000
--- a/tools/qmlls/qqmllanguageserver.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef QQMLLANGUAGESERVER_H
-#define QQMLLANGUAGESERVER_H
-
-#include "qlanguageserver_p.h"
-#include "qqmlcodemodel.h"
-#include "textsynchronization.h"
-#include "qmllintsuggestions.h"
-#include "workspace.h"
-#include "qmlcompletionsupport.h"
-#include "../shared/qqmltoolingsettings.h"
-
-QT_BEGIN_NAMESPACE
-namespace QmlLsp {
-
-/*
- * The language server protocol calls "URI" what QML calls "URL".
- * According to RFC 3986, a URL is a special case of URI that not only
- * identifies a resource but also shows how to access it.
- * In QML, however, URIs are distinct from URLs. URIs are the
- * identifiers of modules, for example "QtQuick.Controls".
- * In order to not confuse the terms we interpret language server URIs
- * as URLs in the QML code model.
- * This method marks a point of translation between the terms, but does
- * not have to change the actual URI/URL.
- */
-inline QByteArray lspUriToQmlUrl(const QByteArray &uri) { return uri; }
-
-class QQmlLanguageServer : public QLanguageServerModule
-{
- Q_OBJECT
-public:
- QQmlLanguageServer(std::function<void(const QByteArray &)> sendData,
- QQmlToolingSettings *settings = nullptr);
-
- QString name() const final;
- void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) final;
- void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &serverInfo) final;
-
- int returnValue() const;
-
- QQmlCodeModel *codeModel();
- QLanguageServer *server();
- TextSynchronization *textSynchronization();
- QmlLintSuggestions *lint();
- WorkspaceHandlers *worspace();
-
-public Q_SLOTS:
- void exit();
- void errorExit();
-
-private:
- QQmlCodeModel m_codeModel;
- QLanguageServer m_server;
- TextSynchronization m_textSynchronization;
- QmlLintSuggestions m_lint;
- WorkspaceHandlers m_workspace;
- QmlCompletionSupport m_completionSupport;
- int m_returnValue = 1;
-};
-
-} // namespace QmlLsp
-QT_END_NAMESPACE
-#endif // QQMLLANGUAGESERVER_H
diff --git a/tools/qmlls/textblock.cpp b/tools/qmlls/textblock.cpp
deleted file mode 100644
index 04a228922e..0000000000
--- a/tools/qmlls/textblock.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "textblock.h"
-#include "textdocument.h"
-
-#include <QtCore/qstring.h>
-
-namespace Utils {
-
-bool TextBlock::isValid() const
-{
- return m_document;
-}
-
-void TextBlock::setBlockNumber(int blockNumber)
-{
- m_blockNumber = blockNumber;
-}
-
-int TextBlock::blockNumber() const
-{
- return m_blockNumber;
-}
-
-void TextBlock::setPosition(int position)
-{
- m_position = position;
-}
-
-int TextBlock::position() const
-{
- return m_position;
-}
-
-void TextBlock::setLength(int length)
-{
- m_length = length;
-}
-
-int TextBlock::length() const
-{
- return m_length;
-}
-
-TextBlock TextBlock::next() const
-{
- return m_document->findBlockByNumber(m_blockNumber + 1);
-}
-
-TextBlock TextBlock::previous() const
-{
- return m_document->findBlockByNumber(m_blockNumber - 1);
-}
-
-int TextBlock::userState() const
-{
- return m_document->userState(m_blockNumber);
-}
-
-void TextBlock::setUserState(int state)
-{
- m_document->setUserState(m_blockNumber, state);
-}
-
-void TextBlock::setDocument(TextDocument *document)
-{
- m_document = document;
-}
-
-TextDocument *TextBlock::document() const
-{
- return m_document;
-}
-
-QString TextBlock::text() const
-{
- return document()->toPlainText().mid(position(), length());
-}
-
-int TextBlock::revision() const
-{
- return m_revision;
-}
-
-void TextBlock::setRevision(int rev)
-{
- m_revision = rev;
-}
-
-bool operator==(const TextBlock &t1, const TextBlock &t2)
-{
- return t1.document() == t2.document() && t1.blockNumber() == t2.blockNumber();
-}
-
-bool operator!=(const TextBlock &t1, const TextBlock &t2)
-{
- return !(t1 == t2);
-}
-
-} // namespace Utils
diff --git a/tools/qmlls/textblock.h b/tools/qmlls/textblock.h
deleted file mode 100644
index 593aa88daf..0000000000
--- a/tools/qmlls/textblock.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef TEXTBLOCK_H
-#define TEXTBLOCK_H
-
-#include <QtCore/qstring.h>
-
-namespace Utils {
-
-class TextDocument;
-class TextBlockUserData;
-
-class TextBlock
-{
-public:
- bool isValid() const;
-
- void setBlockNumber(int blockNumber);
- int blockNumber() const;
-
- void setPosition(int position);
- int position() const;
-
- void setLength(int length);
- int length() const;
-
- TextBlock next() const;
- TextBlock previous() const;
-
- int userState() const;
- void setUserState(int state);
-
- bool isVisible() const;
- void setVisible(bool visible);
-
- void setLineCount(int count);
- int lineCount() const;
-
- void setDocument(TextDocument *document);
- TextDocument *document() const;
-
- QString text() const;
-
- int revision() const;
- void setRevision(int rev);
-
- friend bool operator==(const TextBlock &t1, const TextBlock &t2);
- friend bool operator!=(const TextBlock &t1, const TextBlock &t2);
-
-private:
- TextDocument *m_document = nullptr;
- int m_revision = 0;
-
- int m_position = 0;
- int m_length = 0;
- int m_blockNumber = -1;
-};
-
-} // namespace Utils
-
-#endif // TEXTBLOCK_H
diff --git a/tools/qmlls/textcursor.cpp b/tools/qmlls/textcursor.cpp
deleted file mode 100644
index 3a6e7ca080..0000000000
--- a/tools/qmlls/textcursor.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "textcursor.h"
-#include "textdocument.h"
-#include "textblock.h"
-
-namespace Utils {
-
-class TextFrame;
-class TextTable;
-class TextTableCell;
-
-TextCursor::TextCursor(TextDocument *document) : m_document(document) { }
-
-bool TextCursor::movePosition(TextCursor::MoveOperation op, TextCursor::MoveMode mode, int n)
-{
- Q_UNUSED(n);
- switch (op) {
- case NoMove:
- return true;
- case Start:
- m_position = 0;
- break;
- case PreviousCharacter:
- while (--n >= 0) {
- if (m_position == 0)
- return false;
- --m_position;
- }
- break;
- case End:
- m_position = m_document->characterCount();
- break;
- case NextCharacter:
- while (--n >= 0) {
- if (m_position == m_document->characterCount())
- return false;
- ++m_position;
- }
- break;
- }
-
- if (mode == MoveAnchor)
- m_anchor = m_position;
-
- return false;
-}
-
-int TextCursor::position() const
-{
- return m_position;
-}
-
-void TextCursor::setPosition(int pos, Utils::TextCursor::MoveMode mode)
-{
- m_position = pos;
- if (mode == MoveAnchor)
- m_anchor = pos;
-}
-
-QString TextCursor::selectedText() const
-{
- return m_document->toPlainText().mid(qMin(m_position, m_anchor), qAbs(m_position - m_anchor));
-}
-
-void TextCursor::clearSelection()
-{
- m_anchor = m_position;
-}
-
-TextDocument *TextCursor::document() const
-{
- return m_document;
-}
-
-void TextCursor::insertText(const QString &text)
-{
- const QString orig = m_document->toPlainText();
- const QString left = orig.left(qMin(m_position, m_anchor));
- const QString right = orig.mid(qMax(m_position, m_anchor));
- m_document->setPlainText(left + text + right);
-}
-
-TextBlock TextCursor::block() const
-{
- TextBlock current = m_document->firstBlock();
- while (current.isValid()) {
- if (current.position() <= position()
- && current.position() + current.length() > current.position())
- break;
- current = current.next();
- }
- return current;
-}
-
-int TextCursor::positionInBlock() const
-{
- return m_position - block().position();
-}
-
-int TextCursor::blockNumber() const
-{
- return block().blockNumber();
-}
-
-void TextCursor::removeSelectedText()
-{
- insertText(QString());
-}
-
-int TextCursor::selectionEnd() const
-{
- return qMax(m_position, m_anchor);
-}
-
-bool TextCursor::isNull() const
-{
- return m_document == nullptr;
-}
-
-}
diff --git a/tools/qmlls/textcursor.h b/tools/qmlls/textcursor.h
deleted file mode 100644
index 618cd739c3..0000000000
--- a/tools/qmlls/textcursor.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef TEXTCURSOR_H
-#define TEXTCURSOR_H
-
-#include <QtCore/qstring.h>
-
-namespace Utils {
-
-class TextDocument;
-class TextBlock;
-
-class TextCursor
-{
-public:
- enum MoveOperation {
- NoMove,
- Start,
- PreviousCharacter,
- End,
- NextCharacter,
- };
-
- enum MoveMode { MoveAnchor, KeepAnchor };
-
- enum SelectionType { Document };
-
- TextCursor();
- TextCursor(const TextBlock &block);
- TextCursor(TextDocument *document);
-
- bool movePosition(MoveOperation op, MoveMode = MoveAnchor, int n = 1);
- int position() const;
- void setPosition(int pos, MoveMode mode = MoveAnchor);
- QString selectedText() const;
- void clearSelection();
- int anchor() const;
- TextDocument *document() const;
- void insertText(const QString &text);
- TextBlock block() const;
- int positionInBlock() const;
- int blockNumber() const;
-
- void select(SelectionType selection);
-
- bool hasSelection() const;
-
- void removeSelectedText();
- int selectionEnd() const;
-
- bool isNull() const;
-
-private:
- TextDocument *m_document = nullptr;
- int m_position = 0;
- int m_anchor = 0;
-};
-} // namespace Utils
-
-#endif // TEXTCURSOR_H
diff --git a/tools/qmlls/textdocument.cpp b/tools/qmlls/textdocument.cpp
deleted file mode 100644
index b5d4cc6f68..0000000000
--- a/tools/qmlls/textdocument.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "textdocument.h"
-#include "textblock.h"
-
-namespace Utils {
-
-TextDocument::TextDocument(const QString &text)
-{
- setPlainText(text);
-}
-
-TextBlock TextDocument::findBlockByNumber(int blockNumber) const
-{
- return (blockNumber >= 0 && blockNumber < m_blocks.size())
- ? m_blocks.at(blockNumber).textBlock
- : TextBlock();
-}
-
-TextBlock TextDocument::findBlockByLineNumber(int lineNumber) const
-{
- return findBlockByNumber(lineNumber);
-}
-
-QChar TextDocument::characterAt(int pos) const
-{
- return m_content.at(pos);
-}
-
-int TextDocument::characterCount() const
-{
- return m_content.size();
-}
-
-TextBlock TextDocument::begin() const
-{
- return m_blocks.isEmpty() ? TextBlock() : m_blocks.at(0).textBlock;
-}
-
-TextBlock TextDocument::firstBlock() const
-{
- return begin();
-}
-
-TextBlock TextDocument::lastBlock() const
-{
- return m_blocks.isEmpty() ? TextBlock() : m_blocks.last().textBlock;
-}
-
-std::optional<int> TextDocument::version() const
-{
- return m_version;
-}
-
-void TextDocument::setVersion(std::optional<int> v)
-{
- m_version = v;
-}
-
-QString TextDocument::toPlainText() const
-{
- return m_content;
-}
-
-void TextDocument::setPlainText(const QString &text)
-{
- m_content = text;
- m_blocks.clear();
-
- int blockStart = 0;
- int blockNumber = 0;
- while (blockStart < text.size()) {
- Block block;
- block.textBlock.setBlockNumber(blockNumber++);
- block.textBlock.setPosition(blockStart);
- block.textBlock.setDocument(this);
-
- int blockEnd = text.indexOf('\n', blockStart) + 1;
- if (blockEnd == 0)
- blockEnd = text.size();
-
- block.textBlock.setLength(blockEnd - blockStart);
- m_blocks.append(block);
- blockStart = blockEnd;
- }
-}
-
-bool TextDocument::isModified() const
-{
- return m_modified;
-}
-
-void TextDocument::setModified(bool modified)
-{
- m_modified = modified;
-}
-
-void TextDocument::setUserState(int blockNumber, int state)
-{
- if (blockNumber >= 0 && blockNumber < m_blocks.size())
- m_blocks[blockNumber].userState = state;
-}
-
-int TextDocument::userState(int blockNumber) const
-{
- return (blockNumber >= 0 && blockNumber < m_blocks.size()) ? m_blocks[blockNumber].userState
- : -1;
-}
-
-QMutex *TextDocument::mutex() const
-{
- return &m_mutex;
-}
-
-} // namespace Utils
diff --git a/tools/qmlls/textdocument.h b/tools/qmlls/textdocument.h
deleted file mode 100644
index e0db66bfb3..0000000000
--- a/tools/qmlls/textdocument.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef TEXTDOCUMENT_H
-#define TEXTDOCUMENT_H
-
-#include "textblock.h"
-
-#include <QtCore/qchar.h>
-#include <QtCore/qvector.h>
-#include <QtCore/qscopedpointer.h>
-#include <QtCore/qmutex.h>
-
-#include <optional>
-
-namespace Utils {
-
-class TextBlockUserData;
-
-class TextDocument
-{
-public:
- TextDocument() = default;
- explicit TextDocument(const QString &text);
-
- TextBlock findBlockByNumber(int blockNumber) const;
- TextBlock findBlockByLineNumber(int lineNumber) const;
- QChar characterAt(int pos) const;
- int characterCount() const;
- TextBlock begin() const;
- TextBlock firstBlock() const;
- TextBlock lastBlock() const;
-
- std::optional<int> version() const;
- void setVersion(std::optional<int>);
-
- QString toPlainText() const;
- void setPlainText(const QString &text);
-
- bool isModified() const;
- void setModified(bool modified);
-
- void setUndoRedoEnabled(bool enable);
-
- void clear();
-
- void setUserState(int blockNumber, int state);
- int userState(int blockNumber) const;
- QMutex *mutex() const;
-
-private:
- struct Block
- {
- TextBlock textBlock;
- int userState = -1;
- };
-
- QVector<Block> m_blocks;
-
- QString m_content;
- bool m_modified = false;
- std::optional<int> m_version;
- mutable QMutex m_mutex;
-};
-} // namespace Utils
-
-#endif // TEXTDOCUMENT_H
diff --git a/tools/qmlls/textsynchronization.cpp b/tools/qmlls/textsynchronization.cpp
deleted file mode 100644
index dcef4606f7..0000000000
--- a/tools/qmlls/textsynchronization.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "textsynchronization.h"
-#include "qqmllanguageserver.h"
-
-#include "textdocument.h"
-
-using namespace QLspSpecification;
-using namespace Qt::StringLiterals;
-
-QT_BEGIN_NAMESPACE
-
-TextSynchronization::TextSynchronization(QmlLsp::QQmlCodeModel *codeModel, QObject *parent)
- : QLanguageServerModule(parent), m_codeModel(codeModel)
-{
-}
-
-void TextSynchronization::didCloseTextDocument(const DidCloseTextDocumentParams &params)
-{
- m_codeModel->closeOpenFile(QmlLsp::lspUriToQmlUrl(params.textDocument.uri));
-}
-
-void TextSynchronization::didOpenTextDocument(const DidOpenTextDocumentParams &params)
-{
- const TextDocumentItem &item = params.textDocument;
- const QString fileName = m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(item.uri));
- m_codeModel->newOpenFile(QmlLsp::lspUriToQmlUrl(item.uri), item.version,
- QString::fromUtf8(item.text));
-}
-
-void TextSynchronization::didDidChangeTextDocument(const DidChangeTextDocumentParams &params)
-{
- QByteArray url = QmlLsp::lspUriToQmlUrl(params.textDocument.uri);
- const QString fileName = m_codeModel->url2Path(url);
- auto openDoc = m_codeModel->openDocumentByUrl(url);
- std::shared_ptr<Utils::TextDocument> document = openDoc.textDocument;
- if (!document) {
- qCWarning(lspServerLog) << "Ingnoring changes to non open or closed document"
- << QString::fromUtf8(url);
- return;
- }
- const auto &changes = params.contentChanges;
- {
- QMutexLocker l(document->mutex());
- for (const auto &change : changes) {
- if (!change.range) {
- document->setPlainText(QString::fromUtf8(change.text));
- continue;
- }
-
- const auto &range = *change.range;
- const auto &rangeStart = range.start;
- const int start =
- document->findBlockByNumber(rangeStart.line).position() + rangeStart.character;
- const auto &rangeEnd = range.end;
- const int end =
- document->findBlockByNumber(rangeEnd.line).position() + rangeEnd.character;
-
- document->setPlainText(document->toPlainText().replace(start, end - start,
- QString::fromUtf8(change.text)));
- }
- document->setVersion(params.textDocument.version);
- qCDebug(lspServerLog).noquote()
- << "text is\n:----------" << document->toPlainText() << "\n_________";
- }
- m_codeModel->addOpenToUpdate(url);
- m_codeModel->openNeedUpdate();
-}
-
-void TextSynchronization::registerHandlers(QLanguageServer *server, QLanguageServerProtocol *)
-{
- QObject::connect(server->notifySignals(),
- &QLspNotifySignals::receivedDidOpenTextDocumentNotification, this,
- &TextSynchronization::didOpenTextDocument);
-
- QObject::connect(server->notifySignals(),
- &QLspNotifySignals::receivedDidChangeTextDocumentNotification, this,
- &TextSynchronization::didDidChangeTextDocument);
-
- QObject::connect(server->notifySignals(),
- &QLspNotifySignals::receivedDidCloseTextDocumentNotification, this,
- &TextSynchronization::didCloseTextDocument);
-}
-
-QString TextSynchronization::name() const
-{
- return u"TextSynchonization"_s;
-}
-
-void TextSynchronization::setupCapabilities(const QLspSpecification::InitializeParams &,
- QLspSpecification::InitializeResult &serverInfo)
-{
- TextDocumentSyncOptions syncOptions;
- syncOptions.openClose = true;
- syncOptions.change = TextDocumentSyncKind::Incremental;
- serverInfo.capabilities.textDocumentSync = syncOptions;
-}
-
-QT_END_NAMESPACE
diff --git a/tools/qmlls/textsynchronization.h b/tools/qmlls/textsynchronization.h
deleted file mode 100644
index 69076aa81b..0000000000
--- a/tools/qmlls/textsynchronization.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#ifndef TEXTSYNCH_H
-#define TEXTSYNCH_H
-
-#include "qqmlcodemodel.h"
-
-#include "qlanguageserver_p.h"
-
-QT_BEGIN_NAMESPACE
-
-class TextSynchronization : public QLanguageServerModule
-{
- Q_OBJECT
-public:
- TextSynchronization(QmlLsp::QQmlCodeModel *codeModel, QObject *parent = nullptr);
- QString name() const override;
- void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
- void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &) override;
-
-public Q_SLOTS:
- void didOpenTextDocument(const QLspSpecification::DidOpenTextDocumentParams &params);
- void didDidChangeTextDocument(const QLspSpecification::DidChangeTextDocumentParams &params);
- void didCloseTextDocument(const QLspSpecification::DidCloseTextDocumentParams &params);
-
-private:
- QmlLsp::QQmlCodeModel *m_codeModel;
-};
-
-QT_END_NAMESPACE
-#endif // TEXTSYNCH_H
diff --git a/tools/qmlls/workspace.cpp b/tools/qmlls/workspace.cpp
deleted file mode 100644
index a18d5f6f8d..0000000000
--- a/tools/qmlls/workspace.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "workspace.h"
-#include "qqmllanguageserver.h"
-#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
-#include <QtLanguageServer/private/qlspnotifysignals_p.h>
-
-#include <QtCore/qfile.h>
-#include <variant>
-
-QT_BEGIN_NAMESPACE
-using namespace Qt::StringLiterals;
-using namespace QLspSpecification;
-
-void WorkspaceHandlers::registerHandlers(QLanguageServer *server, QLanguageServerProtocol *)
-{
- QObject::connect(server->notifySignals(),
- &QLspNotifySignals::receivedDidChangeWorkspaceFoldersNotification, this,
- [server, this](const DidChangeWorkspaceFoldersParams &params) {
- const WorkspaceFoldersChangeEvent &event = params.event;
-
- const QList<WorkspaceFolder> &removed = event.removed;
- QList<QByteArray> toRemove;
- for (const WorkspaceFolder &folder : removed) {
- toRemove.append(QmlLsp::lspUriToQmlUrl(folder.uri));
- m_codeModel->removeDirectory(
- m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(folder.uri)));
- }
- m_codeModel->removeRootUrls(toRemove);
- const QList<WorkspaceFolder> &added = event.added;
- QList<QByteArray> toAdd;
- QStringList pathsToAdd;
- for (const WorkspaceFolder &folder : added) {
- toAdd.append(QmlLsp::lspUriToQmlUrl(folder.uri));
- pathsToAdd.append(
- m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(folder.uri)));
- }
- m_codeModel->addRootUrls(toAdd);
- m_codeModel->addDirectoriesToIndex(pathsToAdd, server);
- });
-
- QObject::connect(server->notifySignals(),
- &QLspNotifySignals::receivedDidChangeWatchedFilesNotification, this,
- [this](const DidChangeWatchedFilesParams &params) {
- const QList<FileEvent> &changes = params.changes;
- for (const FileEvent &change : changes) {
- const QString filename =
- m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(change.uri));
- switch (FileChangeType(change.type)) {
- case FileChangeType::Created:
- // m_codeModel->addFile(filename);
- break;
- case FileChangeType::Changed: {
- QFile file(filename);
- if (file.open(QIODevice::ReadOnly))
- // m_modelManager->setFileContents(filename, file.readAll());
- break;
- }
- case FileChangeType::Deleted:
- // m_modelManager->removeFile(filename);
- break;
- }
- }
- // update due to dep changes...
- });
-
- QObject::connect(server, &QLanguageServer::clientInitialized, this,
- &WorkspaceHandlers::clientInitialized);
-}
-
-QString WorkspaceHandlers::name() const
-{
- return u"Workspace"_s;
-}
-
-void WorkspaceHandlers::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &serverInfo)
-{
- if (!clientInfo.capabilities.workspace
- || !clientInfo.capabilities.workspace->value("workspaceFolders").toBool(false))
- return;
- WorkspaceFoldersServerCapabilities folders;
- folders.supported = true;
- folders.changeNotifications = true;
- if (!serverInfo.capabilities.workspace)
- serverInfo.capabilities.workspace = QJsonObject();
- serverInfo.capabilities.workspace->insert("workspaceFolders", QTypedJson::toJsonValue(folders));
-}
-
-void WorkspaceHandlers::clientInitialized(QLanguageServer *server)
-{
- QLanguageServerProtocol *protocol = server->protocol();
- const auto clientInfo = server->clientInfo();
- QList<Registration> registrations;
- if (clientInfo.capabilities.workspace
- && clientInfo.capabilities.workspace->value("didChangeWatchedFiles")["dynamicRegistration"]
- .toBool(false)) {
- const int watchAll =
- int(WatchKind::Create) | int(WatchKind::Change) | int(WatchKind::Delete);
- DidChangeWatchedFilesRegistrationOptions watchedFilesParams;
- FileSystemWatcher qmlWatcher;
- qmlWatcher.globPattern = QByteArray("*.{qml,js,mjs}");
- qmlWatcher.kind = watchAll;
- FileSystemWatcher qmldirWatcher;
- qmldirWatcher.globPattern = "qmldir";
- qmldirWatcher.kind = watchAll;
- FileSystemWatcher qmltypesWatcher;
- qmltypesWatcher.globPattern = QByteArray("*.qmltypes");
- qmltypesWatcher.kind = watchAll;
- watchedFilesParams.watchers =
- QList<FileSystemWatcher>({ qmlWatcher, qmldirWatcher, qmltypesWatcher });
- registrations.append(Registration {
- // use ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles as id too
- ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles,
- ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles,
- QTypedJson::toJsonValue(watchedFilesParams) });
- }
-
- if (!registrations.isEmpty()) {
- RegistrationParams params;
- params.registrations = registrations;
- protocol->requestRegistration(
- params,
- []() {
- // successful registration
- },
- [protocol](const ResponseError &err) {
- LogMessageParams msg;
- msg.message = QByteArray("registration of file udates failed, will miss file "
- "changes done outside the editor due to error ");
- msg.message.append(QString::number(err.code).toUtf8());
- if (!err.message.isEmpty())
- msg.message.append(" ");
- msg.message.append(err.message);
- msg.type = MessageType::Warning;
- qCWarning(lspServerLog) << QString::fromUtf8(msg.message);
- protocol->notifyLogMessage(msg);
- });
- }
-
- QSet<QString> rootPaths;
- if (std::holds_alternative<QByteArray>(clientInfo.rootUri)) {
- QString path = m_codeModel->url2Path(
- QmlLsp::lspUriToQmlUrl(std::get<QByteArray>(clientInfo.rootUri)));
- rootPaths.insert(path);
- } else if (clientInfo.rootPath && std::holds_alternative<QByteArray>(*clientInfo.rootPath)) {
- QString path = QString::fromUtf8(std::get<QByteArray>(*clientInfo.rootPath));
- rootPaths.insert(path);
- }
-
- if (clientInfo.workspaceFolders
- && std::holds_alternative<QList<WorkspaceFolder>>(*clientInfo.workspaceFolders)) {
- for (const WorkspaceFolder &workspace :
- std::as_const(std::get<QList<WorkspaceFolder>>(*clientInfo.workspaceFolders))) {
- const QUrl workspaceUrl(QString::fromUtf8(QmlLsp::lspUriToQmlUrl(workspace.uri)));
- rootPaths.insert(workspaceUrl.toLocalFile());
- }
- }
- if (m_status == Status::Indexing)
- m_codeModel->addDirectoriesToIndex(QStringList(rootPaths.begin(), rootPaths.end()), server);
-}
-
-QT_END_NAMESPACE
diff --git a/tools/qmlls/workspace.h b/tools/qmlls/workspace.h
deleted file mode 100644
index 03dcf291d1..0000000000
--- a/tools/qmlls/workspace.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#ifndef WORKSPACE_H
-#define WORKSPACE_H
-
-#include "qqmlcodemodel.h"
-#include "qlanguageserver.h"
-
-QT_BEGIN_NAMESPACE
-
-class WorkspaceHandlers : public QLanguageServerModule
-{
- Q_OBJECT
-public:
- enum class Status { NoIndex, Indexing };
- WorkspaceHandlers(QmlLsp::QQmlCodeModel *codeModel) : m_codeModel(codeModel) { }
- QString name() const override;
- void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
- void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
- QLspSpecification::InitializeResult &) override;
-public Q_SLOTS:
- void clientInitialized(QLanguageServer *);
-
-private:
- QmlLsp::QQmlCodeModel *m_codeModel = nullptr;
- Status m_status = Status::NoIndex;
-};
-
-QT_END_NAMESPACE
-
-#endif // WORKSPACE_H
diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp
index eeb8c6cde3..ead1e729b9 100644
--- a/tools/qmlplugindump/main.cpp
+++ b/tools/qmlplugindump/main.cpp
@@ -30,6 +30,7 @@
#include <QtCore/private/qobject_p.h>
#include <QtCore/private/qmetaobject_p.h>
#include <QtQmlTypeRegistrar/private/qqmljsstreamwriter_p.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
#include <QRegularExpression>
#include <iostream>
@@ -68,13 +69,6 @@ QString inObjectInstantiation;
}
-static QString enquote(const QString &string)
-{
- QString s = string;
- return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\"))
- .replace(QLatin1Char('"'),QLatin1String("\\\"")));
-}
-
struct QmlVersionInfo
{
QString pluginImportUri;
@@ -268,7 +262,7 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine,
QObject *object = nullptr;
if (ty.isSingleton()) {
- QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo();
+ QQmlType::SingletonInstanceInfo::ConstPtr siinfo = ty.singletonInstanceInfo();
if (!siinfo) {
std::cerr << "Internal error, " << qPrintable(tyName)
<< "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")"
@@ -278,7 +272,8 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine,
if (ty.isQObjectSingleton()) {
if (verbose)
std::cerr << "Trying to get singleton for " << qPrintable(tyName)
- << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl;
+ << " (" << qPrintable( QString::fromUtf8(siinfo->typeName) )
+ << ")" << std::endl;
collectReachableMetaObjects(object, &metas, info);
object = QQmlEnginePrivate::get(engine)->singletonInstance<QObject*>(ty);
} else {
@@ -348,7 +343,7 @@ public:
relocatableModuleUri = uri;
}
- QString getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo)
+ QByteArray getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo)
{
const QString module = type.module().isEmpty() ? versionInfo.pluginImportUri
: type.module();
@@ -358,12 +353,14 @@ public:
type.version().hasMinorVersion() ? type.version().minorVersion()
: versionInfo.version.minorVersion());
- const QString versionedElement = type.elementName()
- + QString::fromLatin1(" %1.%2").arg(version.majorVersion()).arg(version.minorVersion());
+ const QByteArray versionedElement
+ = (type.elementName() + QString::fromLatin1(" %1.%2")
+ .arg(version.majorVersion())
+ .arg(version.minorVersion())).toUtf8();
- return enquote((module == relocatableModuleUri)
+ return (module == relocatableModuleUri)
? versionedElement
- : module + QLatin1Char('/') + versionedElement);
+ : module.toUtf8() + '/' + versionedElement;
}
void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr)
@@ -375,30 +372,31 @@ public:
for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) {
QMetaMethod method = meta->method(index);
QByteArray signature = method.methodSignature();
- if (signature == QByteArrayLiteral("destroyed(QObject*)")
- || signature == QByteArrayLiteral("destroyed()")
- || signature == QByteArrayLiteral("deleteLater()"))
+ if (signature == "destroyed(QObject*)"
+ || signature == "destroyed()"
+ || signature == "deleteLater()") {
continue;
+ }
dump(method, implicitSignals, knownAttributes);
}
// and add toString(), destroy() and destroy(int)
- if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, QTypeRevision::zero())) {
- qml->writeStartObject(QLatin1String("Method"));
- qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString")));
+ if (!knownAttributes || !knownAttributes->knownMethod("toString", 0, QTypeRevision::zero())) {
+ qml->writeStartObject("Method");
+ qml->writeStringBinding("name", QLatin1String("toString"));
qml->writeEndObject();
}
- if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, QTypeRevision::zero())) {
- qml->writeStartObject(QLatin1String("Method"));
- qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
+ if (!knownAttributes || !knownAttributes->knownMethod("destroy", 0, QTypeRevision::zero())) {
+ qml->writeStartObject("Method");
+ qml->writeStringBinding("name", QLatin1String("destroy"));
qml->writeEndObject();
}
- if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, QTypeRevision::zero())) {
- qml->writeStartObject(QLatin1String("Method"));
- qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy")));
- qml->writeStartObject(QLatin1String("Parameter"));
- qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay")));
- qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int")));
+ if (!knownAttributes || !knownAttributes->knownMethod("destroy", 1, QTypeRevision::zero())) {
+ qml->writeStartObject("Method");
+ qml->writeStringBinding("name", QLatin1String("destroy"));
+ qml->writeStartObject("Parameter");
+ qml->writeStringBinding("name", QLatin1String("delay"));
+ qml->writeStringBinding("type", QLatin1String("int"));
qml->writeEndObject();
qml->writeEndObject();
}
@@ -408,12 +406,12 @@ public:
}
}
- QString getPrototypeNameForCompositeType(
+ QByteArray getPrototypeNameForCompositeType(
const QMetaObject *metaObject, QList<const QMetaObject *> *objectsToMerge,
const QmlVersionInfo &versionInfo)
{
auto ty = QQmlMetaType::qmlType(metaObject);
- QString prototypeName;
+ QByteArray prototypeName;
if (matchingImportUri(ty, versionInfo)) {
// dynamic meta objects can break things badly
// but extended types are usually fine
@@ -464,35 +462,36 @@ public:
QList<const QMetaObject *> objectsToMerge;
KnownAttributes knownAttributes;
// Get C++ base class name for the composite type
- QString prototypeName = getPrototypeNameForCompositeType(mainMeta, &objectsToMerge,
- versionInfo);
- qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName));
+ QByteArray prototypeName = getPrototypeNameForCompositeType(
+ mainMeta, &objectsToMerge, versionInfo);
+ qml->writeStringBinding("prototype", QUtf8StringView(prototypeName));
- QString qmlTyName = compositeType.qmlTypeName();
- const QString exportString = getExportString(compositeType, versionInfo);
+ const QByteArray exportString = getExportString(compositeType, versionInfo);
// TODO: why don't we simply output the compositeType.elementName() here?
// That would make more sense, but it would change the format quite a bit.
- qml->writeScriptBinding(QLatin1String("name"), exportString);
+ qml->writeStringBinding("name", QUtf8StringView(exportString));
- qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString);
+ qml->writeStringListBinding(
+ "exports", QList<QAnyStringView> { QUtf8StringView(exportString) });
// TODO: shouldn't this be metaObjectRevision().value<quint16>()
// rather than version().minorVersion()
- qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList()
- << QString::number(compositeType.version().minorVersion()));
+ qml->writeArrayBinding(
+ "exportMetaObjectRevisions",
+ QByteArrayList() << QByteArray::number(compositeType.version().minorVersion()));
- qml->writeBooleanBinding(QLatin1String("isComposite"), true);
+ qml->writeBooleanBinding("isComposite", true);
if (compositeType.isSingleton()) {
- qml->writeBooleanBinding(QLatin1String("isCreatable"), false);
- qml->writeBooleanBinding(QLatin1String("isSingleton"), true);
+ qml->writeBooleanBinding("isCreatable", false);
+ qml->writeBooleanBinding("isSingleton", true);
}
for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) {
QMetaClassInfo classInfo = mainMeta->classInfo(index);
- if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) {
- qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value())));
+ if (QUtf8StringView(classInfo.name()) == QUtf8StringView("DefaultProperty")) {
+ qml->writeStringBinding("defaultProperty", QUtf8StringView(classInfo.value()));
break;
}
}
@@ -507,22 +506,29 @@ public:
qml->writeEndObject();
}
- QString getDefaultProperty(const QMetaObject *meta)
+ QByteArray getDefaultProperty(const QMetaObject *meta)
{
for (int index = meta->classInfoCount() - 1; index >= 0; --index) {
QMetaClassInfo classInfo = meta->classInfo(index);
if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) {
- return QLatin1String(classInfo.value());
+ return QByteArray(classInfo.value());
}
}
- return QString();
+ return QByteArray();
}
struct QmlTypeInfo {
QmlTypeInfo() {}
- QmlTypeInfo(const QString &exportString, QTypeRevision revision, const QMetaObject *extendedObject, QByteArray attachedTypeId)
- : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {}
- QString exportString;
+ QmlTypeInfo(
+ const QByteArray &exportString, QTypeRevision revision,
+ const QMetaObject *extendedObject, QByteArray attachedTypeId)
+ : exportString(exportString)
+ , revision(revision)
+ , extendedObject(extendedObject)
+ , attachedTypeId(attachedTypeId)
+ {}
+
+ QByteArray exportString;
QTypeRevision revision = QTypeRevision::zero();
const QMetaObject *extendedObject = nullptr;
QByteArray attachedTypeId;
@@ -533,11 +539,12 @@ public:
qml->writeStartObject("Component");
QByteArray id = convertToId(meta);
- qml->writeScriptBinding(QLatin1String("name"), enquote(id));
+ qml->writeStringBinding("name", QUtf8StringView(id));
// collect type information
QVector<QmlTypeInfo> typeInfo;
- for (QQmlType type : qmlTypesByCppName.value(meta->className())) {
+ const auto types = qmlTypesByCppName.value(meta->className());
+ for (const QQmlType &type : types) {
const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr;
QByteArray attachedTypeId;
if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) {
@@ -546,7 +553,8 @@ public:
if (attachedType != meta)
attachedTypeId = convertToId(attachedType);
}
- const QString exportString = getExportString(type, { QString(), QTypeRevision(), false });
+ const QByteArray exportString = getExportString(
+ type, { QString(), QTypeRevision(), false });
QTypeRevision metaObjectRevision = type.metaObjectRevision();
if (extendedObject) {
// emulate custom metaobjectrevision out of import
@@ -564,7 +572,7 @@ public:
// determine default property
// TODO: support revisioning of default property
- QString defaultProperty = getDefaultProperty(meta);
+ QByteArray defaultProperty = getDefaultProperty(meta);
if (defaultProperty.isEmpty()) {
for (const QmlTypeInfo &iter : typeInfo) {
if (iter.extendedObject) {
@@ -575,31 +583,33 @@ public:
}
}
if (!defaultProperty.isEmpty())
- qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty));
+ qml->writeStringBinding("defaultProperty", defaultProperty);
if (meta->superClass())
- qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass())));
+ qml->writeStringBinding("prototype", convertToId(meta->superClass()));
if (!typeInfo.isEmpty()) {
- QMap<QString, QString> exports; // sort exports
- for (const QmlTypeInfo &iter : typeInfo)
- exports.insert(iter.exportString, QString::number(iter.revision.toEncodedVersion<quint16>()));
+ QMap<QAnyStringView, QByteArray> exports; // sort exports
+ for (const QmlTypeInfo &iter : typeInfo) {
+ exports.insert(
+ QUtf8StringView(iter.exportString),
+ QByteArray::number(iter.revision.toEncodedVersion<quint16>()));
+ }
- QStringList exportStrings = exports.keys();
- QStringList metaObjectRevisions = exports.values();
- qml->writeArrayBinding(QLatin1String("exports"), exportStrings);
+ QByteArrayList metaObjectRevisions = exports.values();
+ qml->writeStringListBinding("exports", exports.keys());
if (isUncreatable)
- qml->writeBooleanBinding(QLatin1String("isCreatable"), false);
+ qml->writeBooleanBinding("isCreatable", false);
if (isSingleton)
- qml->writeBooleanBinding(QLatin1String("isSingleton"), true);
+ qml->writeBooleanBinding("isSingleton", true);
- qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions);
+ qml->writeArrayBinding("exportMetaObjectRevisions", metaObjectRevisions);
for (const QmlTypeInfo &iter : typeInfo) {
if (!iter.attachedTypeId.isEmpty()) {
- qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId));
+ qml->writeStringBinding("attachedType", iter.attachedTypeId);
break;
}
}
@@ -611,7 +621,7 @@ public:
writeMetaContent(meta);
// dump properties from extended metaobjects last
- for (auto iter : typeInfo) {
+ for (const auto &iter : typeInfo) {
if (iter.extendedObject)
dumpMetaProperties(iter.extendedObject, iter.revision);
}
@@ -647,13 +657,13 @@ private:
bool isList = false, isPointer = false;
removePointerAndList(&typeName, &isList, &isPointer);
- qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
+ qml->writeStringBinding("type", QUtf8StringView(typeName));
if (isList)
- qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true"));
+ qml->writeBooleanBinding("isList", true);
if (!isWritable)
- qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true"));
+ qml->writeBooleanBinding("isReadonly", true);
if (isPointer)
- qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true"));
+ qml->writeBooleanBinding("isPointer", true);
}
void dump(const QMetaProperty &prop, QTypeRevision metaRevision = QTypeRevision(),
@@ -667,9 +677,9 @@ private:
if (knownAttributes && knownAttributes->knownProperty(propName, revision))
return;
qml->writeStartObject("Property");
- qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name())));
+ qml->writeStringBinding("name", QUtf8StringView(prop.name()));
if (revision != QTypeRevision::zero())
- qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision.toEncodedVersion<quint16>()));
+ qml->writeNumberBinding("revision", revision.toEncodedVersion<quint16>());
writeTypeProperties(prop.typeName(), prop.isWritable());
qml->writeEndObject();
@@ -682,10 +692,13 @@ private:
for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) {
const QMetaProperty &property = meta->property(index);
dump(property, metaRevision, knownAttributes);
+ const QByteArray changedSignalName =
+ QQmlSignalNames::propertyNameToChangedSignalName(property.name());
if (knownAttributes)
- knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"),
- 0, QTypeRevision::fromEncodedVersion(property.revision()));
- implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name())));
+ knownAttributes->knownMethod(
+ changedSignalName, 0,
+ QTypeRevision::fromEncodedVersion(property.revision()));
+ implicitSignals.insert(changedSignalName);
}
return implicitSignals;
}
@@ -701,13 +714,13 @@ private:
}
QByteArray name = meth.name();
- const QString typeName = convertToId(meth.typeName());
+ const QByteArray typeName = convertToId(meth.typeName());
if (implicitSignals.contains(name)
&& !meth.revision()
&& meth.methodType() == QMetaMethod::Signal
&& meth.parameterNames().isEmpty()
- && typeName == QLatin1String("void")) {
+ && typeName == "void") {
// don't mention implicit signals
return;
}
@@ -716,24 +729,24 @@ private:
if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision))
return;
if (meth.methodType() == QMetaMethod::Signal)
- qml->writeStartObject(QLatin1String("Signal"));
+ qml->writeStartObject("Signal");
else
- qml->writeStartObject(QLatin1String("Method"));
+ qml->writeStartObject("Method");
- qml->writeScriptBinding(QLatin1String("name"), enquote(name));
+ qml->writeStringBinding("name", QUtf8StringView(name));
if (revision != QTypeRevision::zero())
- qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision.toEncodedVersion<quint16>()));
+ qml->writeNumberBinding("revision", revision.toEncodedVersion<quint16>());
- if (typeName != QLatin1String("void"))
- qml->writeScriptBinding(QLatin1String("type"), enquote(typeName));
+ if (typeName != "void")
+ qml->writeStringBinding("type", QUtf8StringView(typeName));
for (int i = 0; i < meth.parameterTypes().size(); ++i) {
QByteArray argName = meth.parameterNames().at(i);
- qml->writeStartObject(QLatin1String("Parameter"));
- if (! argName.isEmpty())
- qml->writeScriptBinding(QLatin1String("name"), enquote(argName));
+ qml->writeStartObject("Parameter");
+ if (!argName.isEmpty())
+ qml->writeStringBinding("name", QUtf8StringView(argName));
writeTypeProperties(meth.parameterTypes().at(i), true);
qml->writeEndObject();
}
@@ -743,17 +756,16 @@ private:
void dump(const QMetaEnum &e)
{
- qml->writeStartObject(QLatin1String("Enum"));
- qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name())));
+ qml->writeStartObject("Enum");
+ qml->writeStringBinding("name", QUtf8StringView(e.name()));
- QList<QPair<QString, QString> > namesValues;
+ QList<QPair<QAnyStringView, int>> namesValues;
const int keyCount = e.keyCount();
namesValues.reserve(keyCount);
- for (int index = 0; index < keyCount; ++index) {
- namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index))));
- }
+ for (int index = 0; index < keyCount; ++index)
+ namesValues.append(qMakePair(QUtf8StringView(e.key(index)), e.value(index)));
- qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues);
+ qml->writeEnumObjectLiteralBinding("values", namesValues);
qml->writeEndObject();
}
};
@@ -1167,17 +1179,17 @@ int main(int argc, char *argv[])
// Merge file.
QStringList mergeDependencies;
- QString mergeComponents;
+ QByteArray mergeComponents;
if (!mergeFile.isEmpty()) {
const QStringList merge = readQmlTypes(mergeFile);
if (!merge.isEmpty()) {
- QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)");
+ static const QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)");
QRegularExpressionMatchIterator i = re.globalMatch(merge[1]);
while (i.hasNext()) {
QRegularExpressionMatch m = i.next();
mergeDependencies << m.captured(1);
}
- mergeComponents = merge [2];
+ mergeComponents = merge[2].toUtf8();
}
}
@@ -1317,16 +1329,20 @@ int main(int argc, char *argv[])
QQmlJSStreamWriter qml(&bytes);
qml.writeStartDocument();
- qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2);
- qml.write(QString("\n"
+ qml.writeLibraryImport("QtQuick.tooling", 1, 2);
+ qml.write("\n"
"// This file describes the plugin-supplied types contained in the library.\n"
"// It is used for QML tooling purposes only.\n"
"//\n"
"// This file was auto-generated by:\n"
- "// '%1 %2'\n"
+ "// '");
+ qml.write(QFileInfo(args.at(0)).baseName().toUtf8());
+ qml.write(" ");
+ qml.write(args.mid(1).join(QLatin1Char(' ')).toUtf8());
+ qml.write("'\n"
"//\n"
"// qmlplugindump is deprecated! You should use qmltyperegistrar instead.\n"
- "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' '))));
+ "\n");
qml.writeStartObject("Module");
// put the metaobjects into a map so they are always dumped in the same order
diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp
index 8f75226fd8..c9e917cc00 100644
--- a/tools/qmlprofiler/qmlprofilerdata.cpp
+++ b/tools/qmlprofiler/qmlprofilerdata.cpp
@@ -3,14 +3,12 @@
#include "qmlprofilerdata.h"
-#include <QStringList>
-#include <QUrl>
-#include <QHash>
-#include <QFile>
-#include <QXmlStreamReader>
-#include <QRegularExpression>
-#include <QQueue>
-#include <QStack>
+#include <QtCore/qfile.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qxmlstream.h>
+#include <QtCore/qxpfunctional.h>
#include <limits>
@@ -375,6 +373,132 @@ private:
QXmlStreamWriter stream;
};
+struct DataIterator
+{
+ DataIterator(
+ const QmlProfilerDataPrivate *d,
+ qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> &&sendEvent)
+ : d(d)
+ , sendEvent(std::move(sendEvent))
+ {}
+
+ void run();
+
+private:
+ void handleRangeEvent(const QQmlProfilerEvent &event, const QQmlProfilerEventType &type);
+ void sendPending();
+ void endLevel0();
+
+ const QmlProfilerDataPrivate *d = nullptr;
+ const qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> sendEvent;
+
+ QQueue<QQmlProfilerEvent> pointEvents;
+ QList<QQmlProfilerEvent> rangeStarts[MaximumRangeType];
+ QList<qint64> rangeEnds[MaximumRangeType];
+
+ int level = 0;
+};
+
+void DataIterator::handleRangeEvent(
+ const QQmlProfilerEvent &event, const QQmlProfilerEventType &type)
+{
+ QList<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()];
+ switch (event.rangeStage()) {
+ case RangeStart: {
+ ++level;
+ starts.append(event);
+ break;
+ }
+ case RangeEnd: {
+ const qint64 invalidTimestamp = -1;
+ QList<qint64> &ends = rangeEnds[type.rangeType()];
+
+ // -1 because all valid timestamps are >= 0.
+ ends.resize(starts.size(), invalidTimestamp);
+
+ qsizetype i = starts.size();
+ while (ends[--i] != invalidTimestamp) {}
+
+ Q_ASSERT(i >= 0);
+ Q_ASSERT(starts[i].timestamp() <= event.timestamp());
+
+ ends[i] = event.timestamp();
+ if (--level == 0)
+ endLevel0();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void DataIterator::sendPending()
+{
+ // Send all pending events in the order of their start times.
+
+ qsizetype index[MaximumRangeType] = { 0, 0, 0, 0, 0, 0 };
+ while (true) {
+
+ // Find the range type with the minimum start time.
+ qsizetype minimum = MaximumRangeType;
+ qint64 minimumTime = std::numeric_limits<qint64>::max();
+ for (qsizetype i = 0; i < MaximumRangeType; ++i) {
+ const QList<QQmlProfilerEvent> &starts = rangeStarts[i];
+ if (starts.size() == index[i])
+ continue;
+ const qint64 timestamp = starts[index[i]].timestamp();
+ if (timestamp < minimumTime) {
+ minimumTime = timestamp;
+ minimum = i;
+ }
+ }
+ if (minimum == MaximumRangeType)
+ break;
+
+ // Send all point events that happened before the range we've found.
+ while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime)
+ sendEvent(pointEvents.dequeue(), 0);
+
+ // Send the range itself
+ sendEvent(rangeStarts[minimum][index[minimum]],
+ rangeEnds[minimum][index[minimum]] - minimumTime);
+
+ // Bump the index so that we don't send the same range again
+ ++index[minimum];
+ }
+}
+
+void DataIterator::endLevel0()
+{
+ sendPending();
+ for (qsizetype i = 0; i < MaximumRangeType; ++i) {
+ rangeStarts[i].clear();
+ rangeEnds[i].clear();
+ }
+}
+
+void DataIterator::run()
+{
+ for (const QQmlProfilerEvent &event : std::as_const(d->events)) {
+ const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex());
+ if (type.rangeType() != MaximumRangeType)
+ handleRangeEvent(event, type);
+ else if (level == 0)
+ sendEvent(event, 0);
+ else
+ pointEvents.enqueue(event);
+ }
+
+ for (qsizetype i = 0; i < MaximumRangeType; ++i) {
+ while (rangeEnds[i].size() < rangeStarts[i].size()) {
+ rangeEnds[i].append(d->traceEndTime);
+ --level;
+ }
+ }
+
+ sendPending();
+}
+
bool QmlProfilerData::save(const QString &filename)
{
if (isEmpty()) {
@@ -442,6 +566,7 @@ bool QmlProfilerData::save(const QString &filename)
stream.writeStartElement("profilerDataModel");
auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) {
+ Q_ASSERT(duration >= 0);
const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex());
stream.writeStartElement("range");
stream.writeAttribute("startTime", event.timestamp());
@@ -481,74 +606,7 @@ bool QmlProfilerData::save(const QString &filename)
stream.writeEndElement();
};
- QQueue<QQmlProfilerEvent> pointEvents;
- QQueue<QQmlProfilerEvent> rangeStarts[MaximumRangeType];
- QStack<qint64> rangeEnds[MaximumRangeType];
- int level = 0;
-
- auto sendPending = [&]() {
- forever {
- int minimum = MaximumRangeType;
- qint64 minimumTime = std::numeric_limits<qint64>::max();
- for (int i = 0; i < MaximumRangeType; ++i) {
- const QQueue<QQmlProfilerEvent> &starts = rangeStarts[i];
- if (starts.isEmpty())
- continue;
- if (starts.head().timestamp() < minimumTime) {
- minimumTime = starts.head().timestamp();
- minimum = i;
- }
- }
- if (minimum == MaximumRangeType)
- break;
-
- while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime)
- sendEvent(pointEvents.dequeue());
-
- sendEvent(rangeStarts[minimum].dequeue(),
- rangeEnds[minimum].pop() - minimumTime);
- }
- };
-
- for (const QQmlProfilerEvent &event : std::as_const(d->events)) {
- const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex());
-
- if (type.rangeType() != MaximumRangeType) {
- QQueue<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()];
- switch (event.rangeStage()) {
- case RangeStart: {
- ++level;
- starts.enqueue(event);
- break;
- }
- case RangeEnd: {
- QStack<qint64> &ends = rangeEnds[type.rangeType()];
- if (starts.size() > ends.size()) {
- ends.push(event.timestamp());
- if (--level == 0)
- sendPending();
- }
- break;
- }
- default:
- break;
- }
- } else {
- if (level == 0)
- sendEvent(event);
- else
- pointEvents.enqueue(event);
- }
- }
-
- for (int i = 0; i < MaximumRangeType; ++i) {
- while (rangeEnds[i].size() < rangeStarts[i].size()) {
- rangeEnds[i].push(d->traceEndTime);
- --level;
- }
- }
-
- sendPending();
+ DataIterator(d, std::move(sendEvent)).run();
stream.writeEndElement(); // profilerDataModel
diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt
index 8e7ce0a586..42e47e24da 100644
--- a/tools/qmltc/CMakeLists.txt
+++ b/tools/qmltc/CMakeLists.txt
@@ -3,7 +3,7 @@
qt_get_tool_target_name(target_name qmltc)
qt_internal_add_tool(${target_name}
- TARGET_DESCRIPTION "QML Type Compiler"
+ TARGET_DESCRIPTION "QML type compiler"
TOOLS_TARGET Qml
SOURCES
main.cpp
diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp
index 6ef0afe105..1899f4087e 100644
--- a/tools/qmltc/main.cpp
+++ b/tools/qmltc/main.cpp
@@ -9,6 +9,7 @@
#include <private/qqmljscompiler_p.h>
#include <private/qqmljsresourcefilemapper_p.h>
+#include <private/qqmljsutils_p.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qurl.h>
@@ -24,6 +25,8 @@
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
+#include <QtQmlCompiler/qqmlsa.h>
+#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h>
#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
@@ -31,8 +34,8 @@ using namespace Qt::StringLiterals;
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
{
- for (const QQmlJSLogger::Category &category : logger.categories()) {
- if (category == qmlUnusedImports)
+ for (const QQmlJS::LoggerCategory &category : logger.categories()) {
+ if (category.id() == qmlUnusedImports)
continue;
logger.setCategoryLevel(category.id(), QtCriticalMsg);
logger.setCategoryIgnored(category.id(), false);
@@ -99,6 +102,25 @@ int main(int argc, char **argv)
QCoreApplication::translate("main", "namespace")
};
parser.addOption(namespaceOption);
+ QCommandLineOption moduleOption{
+ u"module"_s,
+ QCoreApplication::translate("main",
+ "Name of the QML module that this QML code belongs to."),
+ QCoreApplication::translate("main", "module")
+ };
+ parser.addOption(moduleOption);
+ QCommandLineOption exportOption{ u"export"_s,
+ QCoreApplication::translate(
+ "main", "Export macro used in the generated C++ code"),
+ QCoreApplication::translate("main", "export") };
+ parser.addOption(exportOption);
+ QCommandLineOption exportIncludeOption{
+ u"exportInclude"_s,
+ QCoreApplication::translate(
+ "main", "Header defining the export macro to be used in the generated C++ code"),
+ QCoreApplication::translate("main", "exportInclude")
+ };
+ parser.addOption(exportIncludeOption);
parser.process(app);
@@ -153,7 +175,7 @@ int main(int argc, char **argv)
if (!parser.isSet(bareOption))
importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
- QStringList qmldirFiles = parser.values(qmldirOption);
+ QStringList qmldirFiles = QQmlJSUtils::cleanPaths(parser.values(qmldirOption));
QString outputCppFile;
if (!parser.isSet(outputCppOption)) {
@@ -224,6 +246,8 @@ int main(int argc, char **argv)
info.outputHFile = parser.value(outputHOption);
info.resourcePath = firstQml(paths);
info.outputNamespace = parser.value(namespaceOption);
+ info.exportMacro = parser.value(exportOption);
+ info.exportInclude = parser.value(exportIncludeOption);
if (info.outputCppFile.isEmpty()) {
fprintf(stderr, "An output C++ file is required. Pass one using --impl");
@@ -236,24 +260,37 @@ int main(int argc, char **argv)
QQmlJSImporter importer { importPaths, &mapper };
importer.setMetaDataMapper(&metaDataMapper);
- auto createQmltcVisitor = [](const QQmlJSScope::Ptr &root, QQmlJSImporter *importer,
- QQmlJSLogger *logger, const QString &implicitImportDirectory,
- const QStringList &qmldirFiles) -> QQmlJSImportVisitor * {
- return new QmltcVisitor(root, importer, logger, implicitImportDirectory, qmldirFiles);
+ auto qmltcVisitor = [](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self,
+ const QQmlJSImporter::ImportVisitorPrerequisites &p) {
+ QmltcVisitor v(p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles);
+ QQmlJS::AST::Node::accept(rootNode, &v);
};
- importer.setImportVisitorCreator(createQmltcVisitor);
+ importer.setImportVisitor(qmltcVisitor);
QQmlJSLogger logger;
logger.setFileName(url);
logger.setCode(sourceCode);
setupLogger(logger);
- QmltcVisitor visitor(QQmlJSScope::create(), &importer, &logger,
+ auto currentScope = QQmlJSScope::create();
+ if (parser.isSet(moduleOption))
+ currentScope->setOwnModuleName(parser.value(moduleOption));
+
+ QmltcVisitor visitor(currentScope, &importer, &logger,
QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles);
visitor.setMode(QmltcVisitor::Compile);
QmltcTypeResolver typeResolver { &importer };
typeResolver.init(&visitor, qmlParser.rootNode());
+ using PassManagerPtr =
+ std::unique_ptr<QQmlSA::PassManager,
+ decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>;
+ PassManagerPtr passMan(QQmlSA::PassManagerPrivate::createPassManager(&visitor, &typeResolver),
+ &QQmlSA::PassManagerPrivate::deletePassManager);
+ passMan->registerPropertyPass(std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()),
+ QString(), QString(), QString());
+ passMan->analyze(QQmlJSScope::createQQmlSAElement(visitor.result()));
+
if (logger.hasErrors())
return EXIT_FAILURE;
diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp
index 8010f1066c..134de0f98e 100644
--- a/tools/qmltc/qmltccodewriter.cpp
+++ b/tools/qmltc/qmltccodewriter.cpp
@@ -42,14 +42,14 @@ static QString getFunctionCategory(const QmltcMethod &method)
{
QString category = getFunctionCategory(static_cast<const QmltcMethodBase &>(method));
switch (method.type) {
- case QQmlJSMetaMethod::Signal:
+ case QQmlJSMetaMethodType::Signal:
category = u"Q_SIGNALS"_s;
break;
- case QQmlJSMetaMethod::Slot:
+ case QQmlJSMetaMethodType::Slot:
category += u" Q_SLOTS"_s;
break;
- case QQmlJSMetaMethod::Method:
- case QQmlJSMetaMethod::StaticMethod:
+ case QQmlJSMetaMethodType::Method:
+ case QQmlJSMetaMethodType::StaticMethod:
break;
}
return category;
@@ -118,6 +118,7 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString
code.rawAppendToHeader(u"#include <QtCore/qproperty.h>");
code.rawAppendToHeader(u"#include <QtCore/qobject.h>");
code.rawAppendToHeader(u"#include <QtCore/qcoreapplication.h>");
+ code.rawAppendToHeader(u"#include <QtCore/qxpfunctional.h>");
code.rawAppendToHeader(u"#include <QtQml/qqmlengine.h>");
code.rawAppendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution
code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties
@@ -160,6 +161,64 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString
}
}
+void QmltcCodeWriter::write(QmltcOutputWrapper &code,
+ const QmltcPropertyInitializer &propertyInitializer,
+ const QmltcType &wrappedType)
+{
+ code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {");
+
+ {
+ {
+ [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
+
+ code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";");
+ }
+
+ code.rawAppendToHeader(u"public:"_s);
+
+ [[maybe_unused]] QmltcOutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name);
+ {
+ [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
+
+ write(code, propertyInitializer.constructor);
+ code.rawAppendToHeader(u""); // blank line
+
+ for (const auto &propertySetter : propertyInitializer.propertySetters) {
+ write(code, propertySetter);
+ }
+ }
+
+ code.rawAppendToHeader(u""); // blank line
+ code.rawAppendToHeader(u"private:"_s);
+
+ {
+ [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
+
+ write(code, propertyInitializer.component);
+ write(code, propertyInitializer.initializedCache);
+ }
+ }
+
+ code.rawAppendToHeader(u"};"_s);
+ code.rawAppendToHeader(u""); // blank line
+}
+
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle)
+{
+ code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {");
+
+ {
+ [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
+
+ for (const auto &member : requiredPropertiesBundle.members) {
+ write(code, member);
+ }
+ }
+
+ code.rawAppendToHeader(u"};"_s);
+ code.rawAppendToHeader(u""); // blank line
+}
+
void QmltcCodeWriter::writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath,
const QString &outNamespace)
{
@@ -187,12 +246,14 @@ static void writeToFile(const QString &path, const QByteArray &data)
QFileInfo fi(path);
if (fi.exists() && fi.size() == data.size()) {
QFile oldFile(path);
- oldFile.open(QIODevice::ReadOnly);
- if (oldFile.readAll() == data)
- return;
+ if (oldFile.open(QIODevice::ReadOnly)) {
+ if (oldFile.readAll() == data)
+ return;
+ }
}
QFile file(path);
- file.open(QIODevice::WriteOnly);
+ if (!file.open(QIODevice::WriteOnly))
+ qFatal("Could not open file %s", qPrintable(path));
file.write(data);
}
@@ -209,7 +270,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra
code.rawAppendToHeader(u"class " + type.cppType + u";");
// write all the types and their content
for (const QmltcType &type : std::as_const(program.compiledTypes))
- write(code, type);
+ write(code, type, program.exportMacro);
// add typeCount definitions. after all types have been written down (so
// they are now complete types as per C++). practically, this only concerns
@@ -251,10 +312,14 @@ static void dumpFunctions(QmltcOutputWrapper &code, const QList<QmltcMethod> &fu
}
}
-void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type)
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type,
+ const QString &exportMacro)
{
const auto constructClassString = [&]() {
- QString str = u"class " + type.cppType;
+ QString str = u"class "_s;
+ if (!exportMacro.isEmpty())
+ str.append(exportMacro).append(u" "_s);
+ str.append(type.cppType);
QStringList nonEmptyBaseClasses;
nonEmptyBaseClasses.reserve(type.baseClasses.size());
std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(),
@@ -287,6 +352,12 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type)
code.rawAppendToHeader(u"/* External C++ API */");
code.rawAppendToHeader(u"public:", -1);
+ if (!type.propertyInitializer.name.isEmpty())
+ write(code, type.propertyInitializer, type);
+
+ if (type.requiredPropertiesBundle)
+ write(code, *type.requiredPropertiesBundle);
+
// NB: when non-document root, the externalCtor won't be public - but we
// really don't care about the output format of such types
if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) {
@@ -340,7 +411,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type)
// children
for (const auto &child : std::as_const(type.children))
- QmltcCodeWriter::write(code, child);
+ QmltcCodeWriter::write(code, child, exportMacro);
// (non-visible) functions
dumpFunctions(code, type.functions, std::not_fn(isUserVisibleFunction));
@@ -392,14 +463,14 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method)
{
const auto [hSignature, cppSignature] = functionSignatures(method);
// Note: augment return type with preambles in declaration
- code.rawAppendToHeader((method.type == QQmlJSMetaMethod::StaticMethod
+ code.rawAppendToHeader((method.type == QQmlJSMetaMethodType::StaticMethod
? u"static " + functionReturnType(method)
: functionReturnType(method))
+ u" " + hSignature + u";");
// do not generate method implementation if it is a signal
const auto methodType = method.type;
- if (methodType != QQmlJSMetaMethod::Signal) {
+ if (methodType != QQmlJSMetaMethodType::Signal) {
code.rawAppendToCpp(u""_s); // blank line
if (method.comments.size() > 0) {
code.rawAppendToCpp(u"/*! \\internal"_s);
diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h
index e95777998e..20b0262737 100644
--- a/tools/qmltc/qmltccodewriter.h
+++ b/tools/qmltc/qmltccodewriter.h
@@ -20,13 +20,15 @@ struct QmltcCodeWriter
static void writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath,
const QString &outNamespace);
static void write(QmltcOutputWrapper &code, const QmltcProgram &program);
- static void write(QmltcOutputWrapper &code, const QmltcType &type);
+ static void write(QmltcOutputWrapper &code, const QmltcType &type, const QString &exportMacro);
static void write(QmltcOutputWrapper &code, const QmltcEnum &enumeration);
static void write(QmltcOutputWrapper &code, const QmltcMethod &method);
static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor);
static void write(QmltcOutputWrapper &code, const QmltcDtor &dtor);
static void write(QmltcOutputWrapper &code, const QmltcVariable &var);
static void write(QmltcOutputWrapper &code, const QmltcProperty &prop);
+ static void write(QmltcOutputWrapper &code, const QmltcPropertyInitializer &propertyInitializer, const QmltcType& wrappedType);
+ static void write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle);
private:
static void writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod); // special
diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp
index 4050136ef2..75bd580e07 100644
--- a/tools/qmltc/qmltccompiler.cpp
+++ b/tools/qmltc/qmltccompiler.cpp
@@ -8,6 +8,7 @@
#include "qmltccompilerpieces.h"
#include <QtCore/qloggingcategory.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qqmljsutils_p.h>
#include <algorithm>
@@ -22,6 +23,137 @@ bool qIsReferenceTypeList(const QQmlJSMetaProperty &p)
return false;
}
+static QList<QQmlJSMetaProperty> unboundRequiredProperties(
+ const QQmlJSScope::ConstPtr &type,
+ QmltcTypeResolver *resolver
+) {
+ QList<QQmlJSMetaProperty> requiredProperties{};
+
+ auto isPropertyRequired = [&type, &resolver](const auto &property) {
+ if (!type->isPropertyRequired(property.propertyName()))
+ return false;
+
+ if (type->hasPropertyBindings(property.propertyName()))
+ return false;
+
+ if (property.isAlias()) {
+ QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
+
+ QQmlJSUtils::ResolvedAlias result =
+ QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor);
+
+ if (result.kind != QQmlJSUtils::AliasTarget_Property)
+ return false;
+
+ // If the top level alias targets a property that is in
+ // the top level scope and that property is required, then
+ // we will already pick up the property during one of the
+ // iterations.
+ // Setting the property or the alias is the same so we
+ // discard one of the two, as otherwise we would require
+ // the user to pass two values for the same property ,in
+ // this case the alias.
+ //
+ // For example in:
+ //
+ // ```
+ // Item {
+ // id: self
+ // required property int foo
+ // property alias bar: self.foo
+ // }
+ // ```
+ //
+ // Both foo and bar are required but setting one or the
+ // other is the same operation so that we should choose
+ // only one.
+ if (result.owner == type &&
+ type->isPropertyRequired(result.property.propertyName()))
+ return false;
+
+ if (result.owner->hasPropertyBindings(result.property.propertyName()))
+ return false;
+ }
+
+ return true;
+ };
+
+ const auto properties = type->properties();
+ std::copy_if(properties.cbegin(), properties.cend(),
+ std::back_inserter(requiredProperties), isPropertyRequired);
+ std::sort(requiredProperties.begin(), requiredProperties.end(),
+ [](const auto &left, const auto &right) {
+ return left.propertyName() < right.propertyName();
+ });
+
+ return requiredProperties;
+}
+
+
+// Populates the internal representation for a
+// RequiredPropertiesBundle, a class that acts as a bundle of initial
+// values that should be set for the required properties of a type.
+static void compileRequiredPropertiesBundle(
+ QmltcType &current,
+ const QQmlJSScope::ConstPtr &type,
+ QmltcTypeResolver *resolver
+) {
+
+ QList<QQmlJSMetaProperty> requiredProperties = unboundRequiredProperties(type, resolver);
+
+ if (requiredProperties.isEmpty())
+ return;
+
+ current.requiredPropertiesBundle.emplace();
+ current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s;
+
+ current.requiredPropertiesBundle->members.reserve(requiredProperties.size());
+ std::transform(requiredProperties.cbegin(), requiredProperties.cend(),
+ std::back_inserter(current.requiredPropertiesBundle->members),
+ [](const QQmlJSMetaProperty &property) {
+ QString type = qIsReferenceTypeList(property)
+ ? u"const QList<%1*>&"_s.arg(
+ property.type()->valueType()->internalName())
+ : u"passByConstRefOrValue<%1>"_s.arg(
+ property.type()->augmentedInternalName());
+ return QmltcVariable{ type, property.propertyName() };
+ });
+}
+
+static void compileRootExternalConstructorBody(
+ QmltcType& current,
+ const QQmlJSScope::ConstPtr &type
+) {
+ current.externalCtor.body << u"// document root:"_s;
+ // if it's document root, we want to create our QQmltcObjectCreationBase
+ // that would store all the created objects
+ current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg(
+ type->internalName());
+ current.externalCtor.body
+ << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s;
+ current.externalCtor.body << u"creator.set(0, this);"_s; // special case
+
+ QString initializerName = u"initializer"_s;
+ if (current.requiredPropertiesBundle) {
+ // Compose new initializer based on the initial values for required properties.
+ current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s;
+ for (const auto& member : current.requiredPropertiesBundle->members) {
+ current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s.arg(
+ QmltcPropertyData(member.name).write, member.name
+ );
+ }
+ current.externalCtor.body << u" initializer(propertyInitializer);"_s;
+ current.externalCtor.body << u"};"_s;
+
+ initializerName = u"newInitializer"_s;
+ }
+
+ // now call init
+ current.externalCtor.body << current.init.name
+ + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* "
+ u"endInit */ true, %1);"_s.arg(initializerName);
+};
+
Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s;
@@ -87,6 +219,9 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info)
const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(&a);
const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(&b);
+ if (inlineComponentAName == inlineComponentBName)
+ return false;
+
// the root comes at last, so (a < b) == true when b is the root and a is not
if (inlineComponentAName && !inlineComponentBName)
return true;
@@ -127,7 +262,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info)
};
for (const auto &type : pureTypes) {
- Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
compiledTypes.emplaceBack(); // create empty type
compileType(compiledTypes.back(), type, compile);
}
@@ -142,8 +277,11 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info)
program.cppPath = m_info.outputCppFile;
program.hPath = m_info.outputHFile;
program.outNamespace = m_info.outputNamespace;
+ program.exportMacro = m_info.exportMacro;
program.compiledTypes = compiledTypes;
program.includes = m_visitor->cppIncludeFiles();
+ if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty())
+ program.includes += (m_info.exportInclude);
program.urlMethod = urlMethod;
QmltcOutput out;
@@ -186,7 +324,8 @@ void QmltcCompiler::compileType(
// make document root a friend to allow it to access init and endInit
const QString rootInternalName =
m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName();
- current.otherCode << u"friend class %1;"_s.arg(rootInternalName);
+ if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending
+ current.otherCode << "friend class %1;"_L1.arg(rootInternalName);
}
if (documentRoot || inlineComponent) {
auto name = type->inlineComponentName()
@@ -210,8 +349,11 @@ void QmltcCompiler::compileType(
return scope->parentScope();
return scope;
};
- current.otherCode << u"friend class %1;"_s.arg(
- realQmlScope(type->parentScope())->internalName());
+
+ const auto& realScope = realQmlScope(type->parentScope());
+ if (realScope != rootType) {
+ current.otherCode << u"friend class %1;"_s.arg(realScope->internalName());
+ }
}
// make QQmltcObjectCreationHelper a friend of every type since it provides
@@ -240,6 +382,17 @@ void QmltcCompiler::compileType(
current.finalizeComponent.access = QQmlJSMetaMethod::Protected;
current.handleOnCompleted.access = QQmlJSMetaMethod::Protected;
+ current.propertyInitializer.name = u"PropertyInitializer"_s;
+ current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public;
+ current.propertyInitializer.constructor.name = current.propertyInitializer.name;
+ current.propertyInitializer.constructor.parameterList = {
+ QmltcVariable(u"%1&"_s.arg(current.cppType), u"component"_s)
+ };
+ current.propertyInitializer.component.cppType = current.cppType + u"&";
+ current.propertyInitializer.component.name = u"component"_s;
+ current.propertyInitializer.initializedCache.cppType = u"QSet<QString>"_s;
+ current.propertyInitializer.initializedCache.name = u"initializedCache"_s;
+
current.baselineCtor.name = current.cppType;
current.externalCtor.name = current.cppType;
current.init.name = u"QML_init"_s;
@@ -259,19 +412,40 @@ void QmltcCompiler::compileType(
QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s);
QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s);
QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s);
+ QmltcVariable initializedCache(
+ u"[[maybe_unused]] const QSet<QString>&"_s,
+ u"initializedCache"_s,
+ u"{}"_s
+ );
QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s);
QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s);
current.baselineCtor.parameterList = { parent };
current.endInit.parameterList = { creator, engine };
- current.setComplexBindings.parameterList = { creator, engine };
+ current.setComplexBindings.parameterList = { creator, engine, initializedCache };
current.handleOnCompleted.parameterList = { creator };
if (documentRoot || inlineComponent) {
- current.externalCtor.parameterList = { engine, parent };
- current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag };
+ const QmltcVariable initializer(
+ u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_s.arg(current.propertyInitializer.name),
+ u"initializer"_s,
+ u"[](%1&){}"_s.arg(current.propertyInitializer.name));
+
+ current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer };
current.beginClass.parameterList = { creator, finalizeFlag };
current.completeComponent.parameterList = { creator, finalizeFlag };
current.finalizeComponent.parameterList = { creator, finalizeFlag };
+
+ compileRequiredPropertiesBundle(current, type, m_typeResolver);
+
+ if (current.requiredPropertiesBundle) {
+ QmltcVariable bundle{
+ u"const %1&"_s.arg(current.requiredPropertiesBundle->name),
+ u"requiredPropertiesBundle"_s,
+ };
+ current.externalCtor.parameterList = { engine, bundle, parent, initializer };
+ } else {
+ current.externalCtor.parameterList = { engine, parent, initializer };
+ }
} else {
current.externalCtor.parameterList = { creator, engine, parent };
current.init.parameterList = { creator, engine, ctxtdata };
@@ -295,18 +469,7 @@ void QmltcCompiler::compileType(
// compilation stub:
current.externalCtor.body << u"Q_UNUSED(engine)"_s;
if (documentRoot || inlineComponent) {
- current.externalCtor.body << u"// document root:"_s;
- // if it's document root, we want to create our QQmltcObjectCreationBase
- // that would store all the created objects
- current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg(
- type->internalName());
- current.externalCtor.body
- << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s;
- current.externalCtor.body << u"creator.set(0, this);"_s; // special case
- // now call init
- current.externalCtor.body << current.init.name
- + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* "
- u"endInit */ true);";
+ compileRootExternalConstructorBody(current, type);
} else {
current.externalCtor.body << u"// not document root:"_s;
// just call init, we don't do any setup here otherwise
@@ -321,7 +484,7 @@ void QmltcCompiler::compileType(
staticCreate.comments
<< u"Used by the engine for singleton creation."_s
<< u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s;
- staticCreate.type = QQmlJSMetaMethod::StaticMethod;
+ staticCreate.type = QQmlJSMetaMethodType::StaticMethod;
staticCreate.access = QQmlJSMetaMethod::Public;
staticCreate.name = u"create"_s;
staticCreate.returnType = u"%1 *"_s.arg(current.cppType);
@@ -354,6 +517,102 @@ static Iterator partitionBindings(Iterator first, Iterator last)
});
}
+// Populates the propertyInitializer of the current type based on the
+// available properties.
+//
+// A propertyInitializer is a generated class that provides a
+// restricted interface that only allows setting property values and
+// internally keep tracks of which properties where actually set,
+// intended to be used to allow the user to set up the initial values
+// when creating an instance of a component.
+//
+// For each property of the current type that is known, is not private
+// and is writable, a setter method is generated.
+// Each setter method knows how to set a specific property, so as to
+// provide a strongly typed interface to property setting, as if the
+// relevant C++ type was used directly.
+//
+// Each setter uses the write method for the proprerty when available
+// and otherwise falls back to a the more generic
+// `QObject::setProperty` for properties where a WRITE method is not
+// available or in scope.
+static void compilePropertyInitializer(QmltcType &current, const QQmlJSScope::ConstPtr &type) {
+ static auto isFromExtension = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) {
+ return scope->ownerOfProperty(scope, property.propertyName()).extensionSpecifier != QQmlJSScope::NotExtension;
+ };
+
+ current.propertyInitializer.constructor.initializerList << u"component{component}"_s;
+
+ auto properties = type->properties().values();
+ for (auto& property: properties) {
+ if (property.index() == -1) continue;
+ if (property.isPrivate()) continue;
+ if (!property.isWritable() && !qIsReferenceTypeList(property)) continue;
+
+ const QString name = property.propertyName();
+
+ current.propertyInitializer.propertySetters.emplace_back();
+ auto& compiledSetter = current.propertyInitializer.propertySetters.back();
+
+ compiledSetter.userVisible = true;
+ compiledSetter.returnType = u"void"_s;
+ compiledSetter.name = QmltcPropertyData(property).write;
+
+ if (qIsReferenceTypeList(property)) {
+ compiledSetter.parameterList.emplaceBack(
+ QQmlJSUtils::constRefify(u"QList<%1*>"_s.arg(property.type()->valueType()->internalName())),
+ name + u"_", QString()
+ );
+ } else {
+ compiledSetter.parameterList.emplaceBack(
+ QQmlJSUtils::constRefify(getUnderlyingType(property)), name + u"_", QString()
+ );
+ }
+
+ if (qIsReferenceTypeList(property)) {
+ compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg(
+ current.propertyInitializer.component.name, name
+ );
+ compiledSetter.body << u"list_ref_.clear();"_s;
+ compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(name);
+ compiledSetter.body << u" list_ref_.append(list_item_);"_s;
+ } else if (
+ QQmlJSUtils::bindablePropertyHasDefaultAccessor(property, QQmlJSUtils::PropertyAccessor_Write)
+ ) {
+ compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg(
+ current.propertyInitializer.component.name, property.bindable(), name);
+ } else if (type->hasOwnProperty(name)) {
+ compiledSetter.body << u"%1.%2(%3_);"_s.arg(
+ current.propertyInitializer.component.name, QmltcPropertyData(property).write, name);
+ } else if (property.write().isEmpty() || isFromExtension(property, type)) {
+ // We can end here if a WRITE method is not available or
+ // if the method is available but not in this scope, so
+ // that we fallback to the string-based setters..
+ //
+ // For example, types that makes use of QML_EXTENDED
+ // types, will have the extension types properties
+ // available and with a WRITE method, but the WRITE method
+ // will not be available to the extended type, from C++,
+ // as the type does not directly inherit from the
+ // extension type.
+ //
+ // We specifically scope `setProperty` to `QObject` as
+ // certain types might have shadowed the method.
+ // For example, in QtQuick, some types have a property
+ // called `property` with a `setProperty` WRITE method
+ // that will produce the shadowing.
+ compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg(
+ current.propertyInitializer.component.name, name);
+ } else {
+ compiledSetter.body << u"%1.%2(%3_);"_s.arg(
+ current.propertyInitializer.component.name, property.write(), name);
+ }
+
+ compiledSetter.body << u"%1.insert(\"%2\");"_s.arg(
+ current.propertyInitializer.initializedCache.name, name);
+ }
+}
+
void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::ConstPtr &type)
{
// compile components of a type:
@@ -396,6 +655,7 @@ void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::C
auto bindings = type->ownPropertyBindingsInQmlIROrder();
partitionBindings(bindings.begin(), bindings.end());
+ compilePropertyInitializer(current, type);
compileBinding(current, bindings.begin(), bindings.end(), type, { type });
}
@@ -450,7 +710,7 @@ compileMethodParameters(const QList<QQmlJSMetaParameter> &parameterInfos, bool a
static QString figureReturnType(const QQmlJSMetaMethod &m)
{
const bool isVoidMethod =
- m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal;
+ m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethodType::Signal;
Q_ASSERT(isVoidMethod || m.returnType());
QString type;
if (isVoidMethod) {
@@ -467,10 +727,10 @@ void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
const auto returnType = figureReturnType(m);
const QList<QmltcVariable> compiledParams = compileMethodParameters(m.parameters());
- const auto methodType = QQmlJSMetaMethod::Type(m.methodType());
+ const auto methodType = m.methodType();
QStringList code;
- if (methodType != QQmlJSMetaMethod::Signal) {
+ if (methodType != QQmlJSMetaMethodType::Signal) {
QmltcCodeGenerator urlGenerator { m_url, m_visitor };
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
&code, urlGenerator.urlMethodName() + u"()",
@@ -485,7 +745,7 @@ void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
compiled.body = std::move(code);
compiled.type = methodType;
compiled.access = m.access();
- if (methodType != QQmlJSMetaMethod::Signal) {
+ if (methodType != QQmlJSMetaMethodType::Signal) {
compiled.declarationPrefixes << u"Q_INVOKABLE"_s;
compiled.userVisible = m.access() == QQmlJSMetaMethod::Public;
} else {
@@ -1057,7 +1317,7 @@ void QmltcCompiler::compileObjectBinding(QmltcType &current,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
- Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::Object);
+ Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
@@ -1115,7 +1375,7 @@ void QmltcCompiler::compileObjectBinding(QmltcType &current,
Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s);
if (int id = m_visitor->runtimeId(object); id >= 0) {
- QString idString = m_visitor->addressableScopes().id(object);
+ QString idString = m_visitor->addressableScopes().id(object, object);
if (idString.isEmpty())
idString = u"<unknown>"_s;
QmltcCodeGenerator::generate_setIdValue(block, u"thisContext"_s, id, objectName,
@@ -1154,8 +1414,8 @@ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType &current,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
- Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::ValueSource
- || binding.bindingType() == QQmlJSMetaPropertyBinding::Interceptor);
+ Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource
+ || binding.bindingType() == QQmlSA::BindingType::Interceptor);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
@@ -1163,7 +1423,7 @@ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType &current,
// NB: object is compiled with compileType(), here just need to use it
QSharedPointer<const QQmlJSScope> object;
- if (binding.bindingType() == QQmlJSMetaPropertyBinding::Interceptor)
+ if (binding.bindingType() == QQmlSA::BindingType::Interceptor)
object = binding.interceptorType();
else
object = binding.valueSourceType();
@@ -1212,7 +1472,7 @@ void QmltcCompiler::compileAttachedPropertyBinding(QmltcType &current,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
- Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::AttachedProperty);
+ Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
@@ -1276,7 +1536,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType &current,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
- Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::GroupProperty);
+ Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
@@ -1333,7 +1593,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType &current,
auto it = subbindings.begin();
Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) {
- return x.bindingType() != QQmlJSMetaPropertyBinding::Script;
+ return x.bindingType() != QQmlSA::BindingType::Script;
}));
compile(it, firstScript);
it = firstScript;
@@ -1354,7 +1614,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType &current,
// once the value is written back, process the script bindings
Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) {
- return x.bindingType() == QQmlJSMetaPropertyBinding::Script;
+ return x.bindingType() == QQmlSA::BindingType::Script;
}));
compile(it, subbindings.end());
}
@@ -1368,8 +1628,8 @@ void QmltcCompiler::compileTranslationBinding(QmltcType &current,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
- Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::Translation
- || binding.bindingType() == QQmlJSMetaPropertyBinding::TranslationById);
+ Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation
+ || binding.bindingType() == QQmlSA::BindingType::TranslationById);
const QString &propertyName = binding.propertyName();
@@ -1446,7 +1706,7 @@ void QmltcCompiler::compileBinding(QmltcType &current,
const auto location = binding.sourceLocation();
// make sure group property is not generalized by checking if type really has a property
// called propertyName. If not, it is probably an id.
- if (binding.bindingType() == QQmlJSMetaPropertyBinding::GroupProperty
+ if (binding.bindingType() == QQmlSA::BindingType::GroupProperty
&& type->hasProperty(propertyName)) {
qCWarning(lcQmltcCompiler)
<< QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
@@ -1493,26 +1753,32 @@ void QmltcCompiler::compileBindingByType(QmltcType &current,
accessor.name, constructFromQObject);
};
switch (binding.bindingType()) {
- case QQmlJSMetaPropertyBinding::BoolLiteral: {
+ case QQmlSA::BindingType::BoolLiteral: {
const bool value = binding.boolValue();
assignToProperty(metaProperty, value ? u"true"_s : u"false"_s);
break;
}
- case QQmlJSMetaPropertyBinding::NumberLiteral: {
+ case QQmlSA::BindingType::NumberLiteral: {
assignToProperty(metaProperty, QString::number(binding.numberValue()));
break;
}
- case QQmlJSMetaPropertyBinding::StringLiteral: {
- assignToProperty(metaProperty, QQmlJSUtils::toLiteral(binding.stringValue()));
+ case QQmlSA::BindingType::StringLiteral: {
+ QString value = QQmlJSUtils::toLiteral(binding.stringValue());
+ if (auto type = metaProperty.type()) {
+ if (type->internalName() == u"QUrl"_s) {
+ value = u"QUrl(%1)"_s.arg(value);
+ }
+ }
+ assignToProperty(metaProperty, value);
break;
}
- case QQmlJSMetaPropertyBinding::RegExpLiteral: {
+ case QQmlSA::BindingType::RegExpLiteral: {
const QString value =
u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue()));
assignToProperty(metaProperty, value);
break;
}
- case QQmlJSMetaPropertyBinding::Null: {
+ case QQmlSA::BindingType::Null: {
// poor check: null bindings are only supported for var and objects
Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
|| propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
@@ -1522,38 +1788,38 @@ void QmltcCompiler::compileBindingByType(QmltcType &current,
assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s);
break;
}
- case QQmlJSMetaPropertyBinding::Script: {
+ case QQmlSA::BindingType::Script: {
QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding";
bindingSymbolName.replace(u'.', u'_'); // can happen with group properties
compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
accessor);
break;
}
- case QQmlJSMetaPropertyBinding::Object: {
+ case QQmlSA::BindingType::Object: {
compileObjectBinding(current, binding, type, accessor);
break;
}
- case QQmlJSMetaPropertyBinding::Interceptor:
+ case QQmlSA::BindingType::Interceptor:
Q_FALLTHROUGH();
- case QQmlJSMetaPropertyBinding::ValueSource: {
+ case QQmlSA::BindingType::ValueSource: {
compileValueSourceOrInterceptorBinding(current, binding, type, accessor);
break;
}
- case QQmlJSMetaPropertyBinding::AttachedProperty: {
+ case QQmlSA::BindingType::AttachedProperty: {
compileAttachedPropertyBinding(current, binding, type, accessor);
break;
}
- case QQmlJSMetaPropertyBinding::GroupProperty: {
+ case QQmlSA::BindingType::GroupProperty: {
compileGroupPropertyBinding(current, binding, type, accessor);
break;
}
- case QQmlJSMetaPropertyBinding::TranslationById:
- case QQmlJSMetaPropertyBinding::Translation: {
+ case QQmlSA::BindingType::TranslationById:
+ case QQmlSA::BindingType::Translation: {
compileTranslationBinding(current, binding, type, accessor);
break;
}
- case QQmlJSMetaPropertyBinding::Invalid: {
+ case QQmlSA::BindingType::Invalid: {
recordError(binding.sourceLocation(), u"This binding is invalid"_s);
break;
}
@@ -1617,8 +1883,11 @@ static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope
// index is already added as p.index())
if (type->isSameType(owner))
return;
- if (m == QQmlJSScope::ExtensionNamespace) // extension namespace properties are ignored
+
+ // extension namespace and JavaScript properties are ignored
+ if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript)
return;
+
index += int(type->ownProperties().size());
};
QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, owner, increment);
@@ -1649,10 +1918,10 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
Q_ASSERT(!objectClassName_signal.isEmpty());
Q_ASSERT(!objectClassName_slot.isEmpty());
- const auto signalMethods = objectType->methods(name, QQmlJSMetaMethod::Signal);
+ const auto signalMethods = objectType->methods(name, QQmlJSMetaMethodType::Signal);
Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
QQmlJSMetaMethod signal = signalMethods.at(0);
- Q_ASSERT(signal.methodType() == QQmlJSMetaMethod::Signal);
+ Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal);
const QString signalName = signal.methodName();
const QString slotName = newSymbol(signalName + u"_slot");
@@ -1672,7 +1941,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
objectType->ownRuntimeFunctionIndex(binding.scriptIndex()),
u"this"_s, // Note: because script bindings always use current QML object scope
signalReturnType, slotParameters);
- slotMethod.type = QQmlJSMetaMethod::Slot;
+ slotMethod.type = QQmlJSMetaMethodType::Slot;
current.functions << std::move(slotMethod);
current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&"
@@ -1681,7 +1950,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
};
switch (binding.scriptKind()) {
- case QQmlJSMetaPropertyBinding::Script_PropertyBinding: {
+ case QQmlSA::ScriptBindingKind::PropertyBinding: {
if (!propertyType) {
recordError(binding.sourceLocation(),
u"Binding on property '" + propertyName + u"' of unknown type");
@@ -1723,19 +1992,20 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
property, valueTypeIndex, accessor.name);
break;
}
- case QQmlJSMetaPropertyBinding::Script_SignalHandler: {
- const auto name = QQmlJSUtils::signalName(propertyName);
+ case QQmlSA::ScriptBindingKind::SignalHandler: {
+ const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName);
Q_ASSERT(name.has_value());
compileScriptSignal(*name);
break;
}
- case QQmlJSMetaPropertyBinding::Script_ChangeHandler: {
+ case QQmlSA ::ScriptBindingKind::ChangeHandler: {
const QString objectClassName = objectType->internalName();
const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor");
- const auto signalName = QQmlJSUtils::signalName(propertyName);
+ const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
Q_ASSERT(signalName.has_value()); // an error somewhere else
- const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName);
+ const auto actualProperty =
+ QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName);
Q_ASSERT(actualProperty.has_value()); // an error somewhere else
const auto actualPropertyType = actualProperty->type();
if (!actualPropertyType) {
@@ -1759,9 +2029,12 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
current.children << compileScriptBindingPropertyChangeHandler(
binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName);
+ current.setComplexBindings.body << u"if (!%1.contains(\"%2\"))"_s.arg(
+ current.propertyInitializer.initializedCache.name, propertyName);
+
// TODO: this could be dropped if QQmlEngine::setContextForObject() is
// done before currently generated C++ object is constructed
- current.setComplexBindings.body << bindingSymbolName + u".reset(new QPropertyChangeHandler<"
+ current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<"
+ bindingFunctorName + u">("
+ QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty)
+ u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h
index d5f07aa3b0..3deab6d44e 100644
--- a/tools/qmltc/qmltccompiler.h
+++ b/tools/qmltc/qmltccompiler.h
@@ -25,6 +25,8 @@ struct QmltcCompilerInfo
QString outputHFile;
QString outputNamespace;
QString resourcePath;
+ QString exportMacro;
+ QString exportInclude;
};
class QmltcCompiler
@@ -48,7 +50,7 @@ public:
static bool isComplexBinding(const QQmlJSMetaPropertyBinding &binding)
{
// TODO: translation bindings (once supported) are also complex?
- return binding.bindingType() == QQmlJSMetaPropertyBinding::Script;
+ return binding.bindingType() == QQmlSA::BindingType::Script;
}
private:
@@ -185,14 +187,14 @@ private:
bool hasErrors() const { return m_logger->hasErrors(); }
void recordError(const QQmlJS::SourceLocation &location, const QString &message,
- LoggerWarningId id = qmlCompiler)
+ QQmlJS::LoggerWarningId id = qmlCompiler)
{
// pretty much any compiler error is a critical error (we cannot
// generate code - compilation fails)
m_logger->log(message, id, location);
}
void recordError(const QV4::CompiledData::Location &location, const QString &message,
- LoggerWarningId id = qmlCompiler)
+ QQmlJS::LoggerWarningId id = qmlCompiler)
{
recordError(QQmlJS::SourceLocation { 0, 0, location.line(), location.column() }, message,
id);
diff --git a/tools/qmltc/qmltccompilerpieces.cpp b/tools/qmltc/qmltccompilerpieces.cpp
index 601cf1bbed..cd1735bc07 100644
--- a/tools/qmltc/qmltccompilerpieces.cpp
+++ b/tools/qmltc/qmltccompilerpieces.cpp
@@ -15,8 +15,8 @@ static QString scopeName(const QQmlJSScope::ConstPtr &scope)
{
Q_ASSERT(scope->isFullyResolved());
const auto scopeType = scope->scopeType();
- if (scopeType == QQmlJSScope::GroupedPropertyScope
- || scopeType == QQmlJSScope::AttachedPropertyScope) {
+ if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope
+ || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) {
return scope->baseType()->internalName();
}
return scope->internalName();
@@ -237,15 +237,18 @@ void QmltcCodeGenerator::generate_createBindingOnProperty(
}
*block += prologue;
- *block << value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");";
+ *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName());
+ *block << u" "_s + value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");";
*block += epilogue;
} else {
QString createBindingForNonBindable =
- u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName
+ u" "_s
+ + u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName
+ u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", "
+ QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", "
+ propName + u")";
// Note: in this version, the binding is set implicitly
+ *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName());
*block << createBindingForNonBindable + u";";
}
}
@@ -293,8 +296,8 @@ void QmltcCodeGenerator::generate_createTranslationBindingOnProperty(
QmltcCodeGenerator::PreparedValue
QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value)
{
- auto isDerivedFromBuiltin = [](QQmlJSScope::ConstPtr t, const QString &builtin) {
- for (; t; t = t->baseType()) {
+ auto isDerivedFromBuiltin = [](const QQmlJSScope::ConstPtr &derived, const QString &builtin) {
+ for (QQmlJSScope::ConstPtr t = derived; t; t = t->baseType()) {
if (t->internalName() == builtin)
return true;
}
@@ -302,7 +305,7 @@ QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p,
};
QStringList prologue;
QStringList epilogue;
- auto propType = p.type();
+ const QQmlJSScope::ConstPtr propType = p.type();
if (isDerivedFromBuiltin(propType, u"QVariant"_s)) {
const QString variantName = u"var_" + p.propertyName();
prologue << u"{ // accepts QVariant"_s;
diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h
index bfd22e1b79..3252f19e86 100644
--- a/tools/qmltc/qmltccompilerpieces.h
+++ b/tools/qmltc/qmltccompilerpieces.h
@@ -152,14 +152,17 @@ struct QmltcCodeGenerator
/*!
\internal
- Generates \a{current.init}'s code. The init method sets up a QQmlContext for
- the object and (in case \a type is a document root) calls other object
- creation methods in a well-defined order:
+ Generates \a{current.init}'s code. The init method sets up a
+ QQmlContext for the object and (in case \a type is a document
+ root) calls other object creation methods, and a user-provided
+ initialization callback, in a well-defined order:
1. current.beginClass
2. current.endInit
- 3. current.completeComponent
- 4. current.finalizeComponent
- 5. current.handleOnCompleted
+ 3. user-provided initialization function
+ 4. current.setComplexBindings
+ 5. current.completeComponent
+ 6. current.finalizeComponent
+ 7. current.handleOnCompleted
This function returns a QScopeGuard with the final instructions that have to
be generated at a later point, once everything else is compiled.
@@ -260,7 +263,7 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType &current,
if (int id = visitor->runtimeId(type); id >= 0) {
current.init.body << u"// 3. set id since it is provided"_s;
- QString idString = visitor->addressableScopes().id(type);
+ QString idString = visitor->addressableScopes().id(type, type);
if (idString.isEmpty())
idString = u"<unknown>"_s;
QmltcCodeGenerator::generate_setIdValue(&current.init.body, u"context"_s, id, u"this"_s,
@@ -294,8 +297,14 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType &current,
.arg(current.beginClass.name);
current.init.body << QStringLiteral(" %1(creator, engine);")
.arg(current.endInit.name);
- current.init.body << QStringLiteral(" %1(creator, engine);")
- .arg(current.setComplexBindings.name);
+
+ current.init.body << QStringLiteral(" {");
+ current.init.body << QStringLiteral(" PropertyInitializer propertyInitializer(*this);");
+ current.init.body << QStringLiteral(" initializer(propertyInitializer);");
+ current.init.body << QStringLiteral(" %1(creator, engine, propertyInitializer.initializedCache);").arg(current.setComplexBindings.name);
+ current.init.body << QStringLiteral(" }");
+
+
current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);")
.arg(current.completeComponent.name);
current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);")
diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h
index 8884ddc3a3..de531f718d 100644
--- a/tools/qmltc/qmltcoutputir.h
+++ b/tools/qmltc/qmltcoutputir.h
@@ -75,7 +75,7 @@ struct QmltcMethodBase
struct QmltcMethod : QmltcMethodBase
{
QString returnType; // C++ return type
- QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type
+ QQmlJSMetaMethodType type = QQmlJSMetaMethodType::Method; // Qt function type
// TODO: should be a better way to handle this
bool userVisible = false; // tells if a function is prioritized during the output generation
@@ -92,6 +92,42 @@ struct QmltcDtor : QmltcMethodBase
{
};
+// Represents a generated class that knows how to set the public,
+// writable properties of a compiled QML -> C++ type.
+// This is generally intended to be available for the root of the
+// document to allow the user to set the initial values for
+// properties, when creating a component, with support for strong
+// typing.
+struct QmltcPropertyInitializer {
+ QString name;
+
+ QmltcCtor constructor;
+
+ // A member containing a reference to the object for which the
+ // properties should be set.
+ QmltcVariable component;
+
+ // A member containing a cache of properties that were actually
+ // set that can be referenced later..
+ QmltcVariable initializedCache;
+
+ // Setter methods for each property.
+ QList<QmltcMethod> propertySetters;
+};
+
+// Represents a generated class that contains a bundle of values to
+// initialize the required properties of a type.
+//
+// This is generally intended to be available for the root component
+// of the document, where it will be used as a constructor argument to
+// force the user to provide initial values for the required
+// properties of the constructed type.
+struct QmltcRequiredPropertiesBundle {
+ QString name;
+
+ QList<QmltcVariable> members;
+};
+
// Represents QML -> C++ compiled type
struct QmltcType
{
@@ -131,6 +167,12 @@ struct QmltcType
// needed for singletons
std::optional<QmltcMethod> staticCreate{};
+
+ // A proxy class that provides a restricted interface that only
+ // allows setting the properties of the type.
+ QmltcPropertyInitializer propertyInitializer{};
+
+ std::optional<QmltcRequiredPropertiesBundle> requiredPropertiesBundle{};
};
// Represents whole QML program, compiled to C++
@@ -140,6 +182,8 @@ struct QmltcProgram
QString cppPath; // C++ output .cpp path
QString hPath; // C++ output .h path
QString outNamespace;
+ QString exportMacro; // if not empty, the macro that should be used to export the generated
+ // classes
QSet<QString> includes; // non-default C++ include files
QmltcMethod urlMethod; // returns QUrl of the QML document
diff --git a/tools/qmltc/qmltcpropertyutils.h b/tools/qmltc/qmltcpropertyutils.h
index db26c498dd..8a69e5ef09 100644
--- a/tools/qmltc/qmltcpropertyutils.h
+++ b/tools/qmltc/qmltcpropertyutils.h
@@ -6,6 +6,7 @@
#include <private/qqmljsmetatypes_p.h>
#include <private/qqmljsscope_p.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
QT_BEGIN_NAMESPACE
@@ -35,13 +36,11 @@ struct QmltcPropertyData
QmltcPropertyData(const QString &propertyName)
{
- const QString nameWithUppercase = propertyName[0].toUpper() + propertyName.sliced(1);
-
read = propertyName;
- write = u"set" + nameWithUppercase;
- bindable = u"bindable" + nameWithUppercase;
- notify = propertyName + u"Changed";
- reset = u"reset" + nameWithUppercase;
+ write = QQmlSignalNames::addPrefixToPropertyName(u"set", propertyName);
+ bindable = QQmlSignalNames::addPrefixToPropertyName(u"bindable", propertyName);
+ notify = QQmlSignalNames::propertyNameToChangedSignalName(propertyName);
+ reset = QQmlSignalNames::addPrefixToPropertyName(u"reset", propertyName);
}
QString read;
diff --git a/tools/qmltc/qmltctyperesolver.cpp b/tools/qmltc/qmltctyperesolver.cpp
index 045f3af088..9c53686736 100644
--- a/tools/qmltc/qmltctyperesolver.cpp
+++ b/tools/qmltc/qmltctyperesolver.cpp
@@ -12,15 +12,12 @@
#include <QtCore/qfileinfo.h>
#include <QtCore/qdiriterator.h>
-Q_LOGGING_CATEGORY(lcTypeResolver2, "qml.qmltc.typeresolver", QtInfoMsg);
+Q_STATIC_LOGGING_CATEGORY(lcTypeResolver2, "qml.qmltc.typeresolver", QtInfoMsg);
void QmltcTypeResolver::init(QmltcVisitor *visitor, QQmlJS::AST::Node *program)
{
QQmlJSTypeResolver::init(visitor, program);
- QQmlJSLiteralBindingCheck literalCheck;
- literalCheck.run(visitor, this);
-
m_root = visitor->result();
QQueue<QQmlJSScope::Ptr> objects;
diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp
index 236ad76467..a6ec1f8661 100644
--- a/tools/qmltc/qmltcvisitor.cpp
+++ b/tools/qmltc/qmltcvisitor.cpp
@@ -8,6 +8,7 @@
#include <QtCore/qstack.h>
#include <QtCore/qdir.h>
#include <QtCore/qloggingcategory.h>
+#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qqmljsutils_p.h>
@@ -101,6 +102,9 @@ void QmltcVisitor::findCppIncludes()
// look in type
addCppInclude(type);
+ if (type->isListProperty())
+ addCppInclude(type->valueType());
+
// look in type's base type
auto base = type->baseType();
if (!base && type->isComposite())
@@ -133,8 +137,9 @@ void QmltcVisitor::findCppIncludes()
Q_ASSERT(type);
const auto scopeType = type->scopeType();
- if (scopeType != QQmlJSScope::QMLScope && scopeType != QQmlJSScope::GroupedPropertyScope
- && scopeType != QQmlJSScope::AttachedPropertyScope) {
+ if (scopeType != QQmlSA::ScopeType::QMLScope
+ && scopeType != QQmlSA::ScopeType::GroupedPropertyScope
+ && scopeType != QQmlSA::ScopeType::AttachedPropertyScope) {
continue;
}
@@ -174,7 +179,7 @@ void QmltcVisitor::findCppIncludes()
static void addCleanQmlTypeName(QStringList *names, const QQmlJSScope::ConstPtr &scope)
{
- Q_ASSERT(scope->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(scope->scopeType() == QQmlSA::ScopeType::QMLScope);
Q_ASSERT(!scope->isArrayScope());
Q_ASSERT(!scope->baseTypeName().isEmpty());
// the scope is guaranteed to be a new QML type, so any prefixes (separated
@@ -197,7 +202,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object)
}
// we're not interested in non-QML scopes
- if (m_currentScope->scopeType() != QQmlJSScope::QMLScope)
+ if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope)
return true;
if (m_currentScope->isInlineComponent()) {
@@ -216,7 +221,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object)
void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *object)
{
- if (m_currentScope->scopeType() == QQmlJSScope::QMLScope)
+ if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope)
m_qmlTypeNames.removeLast();
QQmlJSImportVisitor::endVisit(object);
}
@@ -268,7 +273,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember)
owner->addOwnProperty(property);
}
- const QString notifyName = name + u"Changed"_s;
+ const QString notifyName = QQmlSignalNames::propertyNameToChangedSignalName(name);
// also check that notify is already a method of the scope
{
auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope;
@@ -280,7 +285,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember)
u"internal error: %1 found for property '%2'"_s.arg(errorString, name),
qmlCompiler, publicMember->identifierToken);
return false;
- } else if (methods[0].methodType() != QQmlJSMetaMethod::Signal) {
+ } else if (methods[0].methodType() != QQmlJSMetaMethodType::Signal) {
m_logger->log(u"internal error: method %1 of property %2 must be a signal"_s.arg(
notifyName, name),
qmlCompiler, publicMember->identifierToken);
@@ -336,17 +341,17 @@ void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program)
for (const QList<QQmlJSScope::ConstPtr> &qmlTypes : m_pureQmlTypes)
for (const QQmlJSScope::ConstPtr &type : qmlTypes)
- checkForNamingCollisionsWithCpp(type);
+ checkNamesAndTypes(type);
}
QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding)
{
switch (binding.bindingType()) {
- case QQmlJSMetaPropertyBinding::Object:
+ case QQmlSA::BindingType::Object:
return binding.objectType();
- case QQmlJSMetaPropertyBinding::Interceptor:
+ case QQmlSA::BindingType::Interceptor:
return binding.interceptorType();
- case QQmlJSMetaPropertyBinding::ValueSource:
+ case QQmlSA::BindingType::ValueSource:
return binding.valueSourceType();
// TODO: AttachedProperty and GroupProperty are not supported yet,
// but have to also be acknowledged here
@@ -435,7 +440,7 @@ void QmltcVisitor::postVisitResolve(
// match scopes to indices of QmlIR::Object from QmlIR::Document
qsizetype count = 0;
const auto setIndex = [&](const QQmlJSScope::Ptr &current) {
- if (current->scopeType() != QQmlJSScope::QMLScope || current->isArrayScope())
+ if (current->scopeType() != QQmlSA::ScopeType::QMLScope || current->isArrayScope())
return;
Q_ASSERT(!m_qmlIrObjectIndices.contains(current));
m_qmlIrObjectIndices[current] = count;
@@ -547,9 +552,9 @@ void QmltcVisitor::postVisitResolve(
if (scope->isArrayScope()) // special kind of QQmlJSScope::QMLScope
return;
switch (scope->scopeType()) {
- case QQmlJSScope::QMLScope:
- case QQmlJSScope::GroupedPropertyScope:
- case QQmlJSScope::AttachedPropertyScope: {
+ case QQmlSA::ScopeType::QMLScope:
+ case QQmlSA::ScopeType::GroupedPropertyScope:
+ case QQmlSA::ScopeType::AttachedPropertyScope: {
++qmlScopeCount[scope->enclosingInlineComponentName()];
break;
}
@@ -653,7 +658,7 @@ void QmltcVisitor::setupAliases()
}
}
-void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &type)
+void QmltcVisitor::checkNamesAndTypes(const QQmlJSScope::ConstPtr &type)
{
static const QString cppKeywords[] = {
u"alignas"_s,
@@ -672,20 +677,21 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &
u"case"_s,
u"catch"_s,
u"char"_s,
- u"char8_t"_s,
u"char16_t"_s,
u"char32_t"_s,
+ u"char8_t"_s,
u"class"_s,
+ u"co_await"_s,
+ u"co_return"_s,
+ u"co_yield"_s,
u"compl"_s,
u"concept"_s,
u"const"_s,
+ u"const_cast"_s,
u"consteval"_s,
u"constexpr"_s,
- u"const_cast"_s,
+ u"constinit"_s,
u"continue"_s,
- u"co_await"_s,
- u"co_return"_s,
- u"co_yield"_s,
u"decltype"_s,
u"default"_s,
u"delete"_s,
@@ -753,6 +759,7 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &
u"xor"_s,
u"xor_eq"_s,
};
+ Q_ASSERT(std::is_sorted(std::begin(cppKeywords), std::end(cppKeywords)));
const auto isReserved = [&](QStringView word) {
if (word.startsWith(QChar(u'_')) && word.size() >= 2
@@ -769,6 +776,23 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &
qmlCompiler, type->sourceLocation());
};
+ const auto validateType = [&type, this](const QQmlJSScope::ConstPtr &typeToCheck,
+ QStringView name, QStringView errorPrefix) {
+ if (type->moduleName().isEmpty() || typeToCheck.isNull())
+ return;
+
+ if (typeToCheck->isComposite() && typeToCheck->moduleName() != type->moduleName()) {
+ m_logger->log(
+ QStringLiteral(
+ "Can't compile the %1 type \"%2\" to C++ because it "
+ "lives in \"%3\" instead of the current file's \"%4\" QML module.")
+ .arg(errorPrefix, name, typeToCheck->moduleName(), type->moduleName()),
+ qmlCompiler, type->sourceLocation());
+ }
+ };
+
+ validateType(type->baseType(), type->baseTypeName(), u"QML base");
+
const auto enums = type->ownEnumerations();
for (auto it = enums.cbegin(); it != enums.cend(); ++it) {
const QQmlJSMetaEnum e = it.value();
@@ -783,16 +807,23 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &
for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
const QQmlJSMetaProperty &p = it.value();
validate(p.propertyName(), u"Property");
+
+ if (!p.isAlias() && !p.typeName().isEmpty())
+ validateType(p.type(), p.typeName(), u"QML property");
}
const auto methods = type->ownMethods();
for (auto it = methods.cbegin(); it != methods.cend(); ++it) {
const QQmlJSMetaMethod &m = it.value();
validate(m.methodName(), u"Method");
+ if (!m.returnTypeName().isEmpty())
+ validateType(m.returnType(), m.returnTypeName(), u"QML method return");
- const auto parameterNames = m.parameterNames();
- for (const auto &name : parameterNames)
- validate(name, u"Method '%1' parameter"_s.arg(m.methodName()));
+ for (const auto &parameter : m.parameters()) {
+ validate(parameter.name(), u"Method '%1' parameter"_s.arg(m.methodName()));
+ if (!parameter.typeName().isEmpty())
+ validateType(parameter.type(), parameter.typeName(), u"QML parameter");
+ }
}
// TODO: one could also test signal handlers' parameters but we do not store
diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h
index 0ec9349527..111df0e885 100644
--- a/tools/qmltc/qmltcvisitor.h
+++ b/tools/qmltc/qmltcvisitor.h
@@ -22,7 +22,7 @@ class QmltcVisitor : public QQmlJSImportVisitor
void postVisitResolve(const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>>
&qmlIrOrderedBindings);
void setupAliases();
- void checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &type);
+ void checkNamesAndTypes(const QQmlJSScope::ConstPtr &type);
void setRootFilePath();
QString sourceDirectoryPath(const QString &path);
@@ -57,7 +57,7 @@ public:
qsizetype creationIndex(const QQmlJSScope::ConstPtr &type) const
{
- Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
return m_creationIndices.value(type, -1);
}
@@ -68,13 +68,13 @@ public:
qsizetype qmlComponentIndex(const QQmlJSScope::ConstPtr &type) const
{
- Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
return m_syntheticTypeIndices.value(type, -1);
}
qsizetype qmlIrObjectIndex(const QQmlJSScope::ConstPtr &type) const
{
- Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
Q_ASSERT(m_qmlIrObjectIndices.contains(type));
return m_qmlIrObjectIndices.value(type, -1);
}
diff --git a/tools/qmltime/qmltime.h b/tools/qmltime/qmltime.h
index 7812600456..fa0f44a5e4 100644
--- a/tools/qmltime/qmltime.h
+++ b/tools/qmltime/qmltime.h
@@ -38,6 +38,5 @@ private:
QQuickView m_view;
QQuickItem *m_item;
};
-QML_DECLARE_TYPE(Timer);
#endif // QMLTIME_H
diff --git a/tools/qmltyperegistrar/qmltyperegistrar.cpp b/tools/qmltyperegistrar/qmltyperegistrar.cpp
index 5d895bbed7..937c8a356f 100644
--- a/tools/qmltyperegistrar/qmltyperegistrar.cpp
+++ b/tools/qmltyperegistrar/qmltyperegistrar.cpp
@@ -3,12 +3,14 @@
#include <QCoreApplication>
#include <QCommandLineParser>
+#include <QDir>
#include <QFile>
#include <QScopedPointer>
#include <cstdlib>
#include <QtQmlTypeRegistrar/private/qqmltyperegistrar_p.h>
+#include <QtQmlTypeRegistrar/private/qqmltyperegistrarutils_p.h>
using namespace Qt::Literals;
@@ -17,6 +19,11 @@ int main(int argc, char **argv)
// Produce reliably the same output for the same input by disabling QHash's random seeding.
QHashSeed::setDeterministicGlobalSeed();
+ // No, you are not supposed to mess with the message pattern.
+ // Qt Creator wants to read those messages as-is and we want the convenience
+ // of QDebug to print them.
+ qputenv("QT_MESSAGE_PATTERN", "%{if-category}%{category}: %{endif}%{message}");
+
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral("qmltyperegistrar"));
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
@@ -88,6 +95,13 @@ int main(int argc, char **argv)
"want to follow Qt's versioning scheme."));
parser.addOption(followForeignVersioningOption);
+ QCommandLineOption jsroot(QStringLiteral("jsroot"));
+ jsroot.setDescription(
+ QStringLiteral("Use the JavaScript root object's meta types as sole input and do not "
+ "generate any C++ output. Only useful in combination with "
+ "--generate-qmltypes"));
+ parser.addOption(jsroot);
+
QCommandLineOption extract(u"extract"_s);
extract.setDescription(
u"Extract QML types from a module and use QML_FOREIGN to register them"_s);
@@ -104,20 +118,43 @@ int main(int argc, char **argv)
const QString module = parser.value(importNameOption);
+ const QLatin1String jsrootMetaTypes
+ = QLatin1String(":/qt-project.org/meta_types/jsroot_metatypes.json");
+ QStringList files = parser.positionalArguments();
+ if (parser.isSet(jsroot)) {
+ if (parser.isSet(extract)) {
+ error(module) << "If --jsroot is passed, no type registrations can be extracted.";
+ return EXIT_FAILURE;
+ }
+ if (parser.isSet(outputOption)) {
+ error(module) << "If --jsroot is passed, no C++ output can be generated.";
+ return EXIT_FAILURE;
+ }
+ if (!files.isEmpty() || parser.isSet(foreignTypesOption)) {
+ error(module) << "If --jsroot is passed, no further metatypes can be processed.";
+ return EXIT_FAILURE;
+ }
+
+ files.append(jsrootMetaTypes);
+ }
+
MetaTypesJsonProcessor processor(parser.isSet(privateIncludesOption));
- if (!processor.processTypes(parser.positionalArguments()))
+ if (!processor.processTypes(files))
return EXIT_FAILURE;
processor.postProcessTypes();
- if (parser.isSet(foreignTypesOption))
- processor.processForeignTypes(parser.value(foreignTypesOption).split(QLatin1Char(',')));
+ if (!parser.isSet(jsroot)) {
+ processor.processForeignTypes(jsrootMetaTypes);
+ if (parser.isSet(foreignTypesOption))
+ processor.processForeignTypes(parser.value(foreignTypesOption).split(QLatin1Char(',')));
+ }
processor.postProcessForeignTypes();
if (parser.isSet(extract)) {
if (!parser.isSet(outputOption)) {
- fprintf(stderr, "Error: The output file name must be provided\n");
+ error(module) << "The output file name must be provided";
return EXIT_FAILURE;
}
QString baseName = parser.value(outputOption);
@@ -137,25 +174,35 @@ int main(int argc, char **argv)
parser.isSet(followForeignVersioningOption));
typeRegistrar.setTypes(processor.types(), processor.foreignTypes());
- if (parser.isSet(outputOption)) {
- // extract does its own file handling
- QString outputName = parser.value(outputOption);
- QFile file(outputName);
- if (!file.open(QIODeviceBase::WriteOnly)) {
- fprintf(stderr, "Error: Cannot open %s for writing\n", qPrintable(outputName));
- return EXIT_FAILURE;
+ if (!parser.isSet(jsroot)) {
+ if (module.isEmpty()) {
+ warning(module) << "The module name is empty. Cannot generate C++ code";
+ } else if (parser.isSet(outputOption)) {
+ // extract does its own file handling
+ QString outputName = parser.value(outputOption);
+ QFile file(outputName);
+ if (!file.open(QIODeviceBase::WriteOnly)) {
+ error(QDir::toNativeSeparators(outputName))
+ << "Cannot open file for writing:" << file.errorString();
+ return EXIT_FAILURE;
+ }
+ QTextStream output(&file);
+ typeRegistrar.write(output, outputName);
+ } else {
+ QTextStream output(stdout);
+ typeRegistrar.write(output, "stdout");
}
- QTextStream output(&file);
- typeRegistrar.write(output);
- } else {
- QTextStream output(stdout);
- typeRegistrar.write(output);
}
if (!parser.isSet(pluginTypesOption))
return EXIT_SUCCESS;
typeRegistrar.setReferencedTypes(processor.referencedTypes());
- typeRegistrar.generatePluginTypes(parser.value(pluginTypesOption));
+ typeRegistrar.setUsingDeclarations(processor.usingDeclarations());
+ const QString qmltypes = parser.value(pluginTypesOption);
+ if (!typeRegistrar.generatePluginTypes(qmltypes)) {
+ error(qmltypes) << "Cannot generate qmltypes file";
+ return EXIT_FAILURE;
+ }
return EXIT_SUCCESS;
}
diff --git a/tools/shared/qqmltoolingsettings.cpp b/tools/shared/qqmltoolingsettings.cpp
deleted file mode 100644
index dffd59fbc7..0000000000
--- a/tools/shared/qqmltoolingsettings.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "qqmltoolingsettings.h"
-
-#include <algorithm>
-
-#include <QtCore/qdebug.h>
-#include <QtCore/qdir.h>
-#include <QtCore/qfileinfo.h>
-#include <QtCore/qset.h>
-#include <QtCore/qsettings.h>
-#include <QtCore/qstandardpaths.h>
-
-using namespace Qt::StringLiterals;
-
-void QQmlToolingSettings::addOption(const QString &name, QVariant defaultValue)
-{
- m_values[name] = defaultValue;
-}
-
-bool QQmlToolingSettings::read(const QString &settingsFilePath)
-{
- if (!QFileInfo::exists(settingsFilePath))
- return false;
-
- if (m_currentSettingsPath == settingsFilePath)
- return true;
-
- QSettings settings(settingsFilePath, QSettings::IniFormat);
-
- for (const QString &key : settings.allKeys())
- m_values[key] = settings.value(key).toString();
-
- m_currentSettingsPath = settingsFilePath;
-
- return true;
-}
-
-bool QQmlToolingSettings::writeDefaults() const
-{
- const QString path = QFileInfo(u".%1.ini"_s.arg(m_toolName)).absoluteFilePath();
-
- QSettings settings(path, QSettings::IniFormat);
- for (auto it = m_values.constBegin(); it != m_values.constEnd(); ++it) {
- settings.setValue(it.key(), it.value().isNull() ? QString() : it.value());
- }
-
- settings.sync();
-
- if (settings.status() != QSettings::NoError) {
- qWarning() << "Failed to write default settings to" << path
- << "Error:" << settings.status();
- return false;
- }
-
- qInfo() << "Wrote default settings to" << path;
- return true;
-}
-
-bool QQmlToolingSettings::search(const QString &path)
-{
- QFileInfo fileInfo(path);
- QDir dir(fileInfo.isDir() ? path : fileInfo.dir());
-
- QSet<QString> dirs;
-
- const QString settingsFileName = u".%1.ini"_s.arg(m_toolName);
-
- while (dir.exists() && dir.isReadable()) {
- const QString dirPath = dir.absolutePath();
-
- if (m_seenDirectories.contains(dirPath)) {
- const QString cachedIniPath = m_seenDirectories[dirPath];
- if (cachedIniPath.isEmpty())
- return false;
-
- return read(cachedIniPath);
- }
-
- dirs << dirPath;
-
- const QString iniFile = dir.absoluteFilePath(settingsFileName);
-
- if (read(iniFile)) {
- for (const QString &dir : std::as_const(dirs))
- m_seenDirectories[dir] = iniFile;
- return true;
- }
-
- if (!dir.cdUp())
- break;
- }
-
- if (const QString iniFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, u"%1.ini"_s.arg(m_toolName)); !iniFile.isEmpty()) {
- if (read(iniFile)) {
- for (const QString &dir : std::as_const(dirs))
- m_seenDirectories[dir] = iniFile;
- return true;
- }
- }
-
- // No INI file found anywhere, record the failure so we won't have to traverse the entire
- // filesystem again
- for (const QString &dir : std::as_const(dirs))
- m_seenDirectories[dir] = QString();
-
- return false;
-}
-
-QVariant QQmlToolingSettings::value(QString name) const
-{
- return m_values.value(name);
-}
-
-bool QQmlToolingSettings::isSet(QString name) const
-{
- if (!m_values.contains(name))
- return false;
-
- QVariant variant = m_values[name];
-
- // Unset is encoded as an empty string
- return !(variant.canConvert(QMetaType(QMetaType::QString)) && variant.toString().isEmpty());
-}
diff --git a/tools/shared/qqmltoolingsettings.h b/tools/shared/qqmltoolingsettings.h
deleted file mode 100644
index 483147b3e9..0000000000
--- a/tools/shared/qqmltoolingsettings.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#ifndef QQMLTOOLINGSETTINGS_H
-#define QQMLTOOLINGSETTINGS_H
-
-#include <QtCore/qstring.h>
-#include <QtCore/qhash.h>
-#include <QtCore/qvariant.h>
-
-class QQmlToolingSettings
-{
-public:
- QQmlToolingSettings(const QString &toolName) : m_toolName(toolName) { }
-
- void addOption(const QString &name, const QVariant defaultValue = QVariant());
-
- bool writeDefaults() const;
- bool search(const QString &path);
-
- QVariant value(QString name) const;
- bool isSet(QString name) const;
-
-private:
- QString m_toolName;
- QString m_currentSettingsPath;
- QHash<QString, QString> m_seenDirectories;
- QVariantHash m_values;
-
- bool read(const QString &settingsFilePath);
-};
-
-#endif
diff --git a/tools/svgtoqml/CMakeLists.txt b/tools/svgtoqml/CMakeLists.txt
new file mode 100644
index 0000000000..fd6c91e7ea
--- /dev/null
+++ b/tools/svgtoqml/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## svgtoqml Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name svgtoqml)
+qt_internal_add_tool(${target_name}
+ TARGET_DESCRIPTION "SVG to QML Converter"
+ TOOLS_TARGET Quick
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickVectorImageGeneratorPrivate
+)
+qt_internal_return_unless_building_tools()
+
+set(resource_files
+ "main.qml"
+)
+
+qt_internal_add_resource(${target_name} "qml"
+ PREFIX
+ "/"
+ FILES
+ ${resource_files}
+)
diff --git a/tools/svgtoqml/main.cpp b/tools/svgtoqml/main.cpp
new file mode 100644
index 0000000000..51c2f741ca
--- /dev/null
+++ b/tools/svgtoqml/main.cpp
@@ -0,0 +1,141 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QCommandLineParser>
+#include <QFile>
+#include <QQuickWindow>
+#include <QQuickItem>
+#include <QtQuickVectorImageGenerator/private/qquickitemgenerator_p.h>
+#include <QtQuickVectorImageGenerator/private/qquickqmlgenerator_p.h>
+#include <QtQuickVectorImageGenerator/private/qquickvectorimageglobal_p.h>
+
+#define ENABLE_GUI
+
+int main(int argc, char *argv[])
+{
+#ifdef ENABLE_GUI
+ QGuiApplication app(argc, argv);
+#else
+ QCoreApplication app(argc, argv);
+#endif
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription("SVG to QML converter [tech preview]");
+ parser.addHelpOption();
+ parser.addPositionalArgument("input", QCoreApplication::translate("main", "SVG file to read."));
+ parser.addPositionalArgument("output", QCoreApplication::translate("main", "QML file to write."), "[output]");
+
+ QCommandLineOption optimizeOption("optimize-paths",
+ QCoreApplication::translate("main", "Optimize paths for the curve renderer."));
+ parser.addOption(optimizeOption);
+
+ QCommandLineOption curveRendererOption("curve-renderer",
+ QCoreApplication::translate("main", "Use the curve renderer in generated QML."));
+ parser.addOption(curveRendererOption);
+
+ QCommandLineOption typeNameOption(QStringList() << "t" << "type-name",
+ QCoreApplication::translate("main", "Use <typename> for Shape."),
+ QCoreApplication::translate("main", "typename"));
+ parser.addOption(typeNameOption);
+
+ QCommandLineOption copyrightOption("copyright-statement",
+ QCoreApplication::translate("main", "Add <string> as a comment at the start of the generated file."),
+ QCoreApplication::translate("main", "string"));
+ parser.addOption(copyrightOption);
+
+ QCommandLineOption outlineModeOption("outline-stroke-mode",
+ QCoreApplication::translate("main", "Stroke the outline (contour) of the filled shape instead of "
+ "the original path. Also sets optimize-paths."));
+ parser.addOption(outlineModeOption);
+
+ QCommandLineOption keepPathsOption("keep-external-paths",
+ QCoreApplication::translate("main", "Any paths to external files will be retained in the QML output. "
+ "The paths will be reformatted as relative to the output file. If "
+ "this is not enabled, copies of the file will be saved to the asset output "
+ "directory. Embedded data will still be saved to files, even if "
+ "this option is set."));
+ parser.addOption(keepPathsOption);
+
+ QCommandLineOption assetOutputDirectoryOption("asset-output-directory",
+ QCoreApplication::translate("main", "If the SVG refers to external or embedded files, such as images, these "
+ "will be copied into the same directory as the output QML file by default. "
+ "Set the asset output directory to override the location."),
+ QCoreApplication::translate("main", "directory"));
+ parser.addOption(assetOutputDirectoryOption);
+
+ QCommandLineOption assetOutputPrefixOption("asset-output-prefix",
+ QCoreApplication::translate("main", "If the SVG refers to external or embedded files, such as images, these "
+ "will be copied to files with unique identifiers. By default, the files will be prefixed "
+ "with \"svg_asset_\". Set the asset output prefix to override the prefix."),
+ QCoreApplication::translate("main", "prefix"));
+ parser.addOption(assetOutputPrefixOption);
+
+#ifdef ENABLE_GUI
+ QCommandLineOption guiOption(QStringList() << "v" << "view",
+ QCoreApplication::translate("main", "Display the generated QML in a window. This is the default behavior if no "
+ "output file is specified."));
+ parser.addOption(guiOption);
+#endif
+ parser.process(app);
+ const QStringList args = parser.positionalArguments();
+ if (args.size() < 1) {
+ parser.showHelp(1);
+ }
+
+ const QString inFileName = args.at(0);
+
+ QString commentString = QLatin1String("Generated from SVG file %1").arg(inFileName);
+
+ const auto outFileName = args.size() > 1 ? args.at(1) : QString{};
+ const auto typeName = parser.value(typeNameOption);
+ const auto assetOutputDirectory = parser.value(assetOutputDirectoryOption);
+ const auto assetOutputPrefix = parser.value(assetOutputPrefixOption);
+ const bool keepPaths = parser.isSet(keepPathsOption);
+ auto copyrightString = parser.value(copyrightOption);
+
+ if (!copyrightString.isEmpty()) {
+ copyrightString = copyrightString.replace("\\n", "\n");
+ commentString = copyrightString + u"\n" + commentString;
+ }
+
+ QQuickVectorImageGenerator::GeneratorFlags flags;
+ if (parser.isSet(curveRendererOption))
+ flags |= QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer;
+ if (parser.isSet(optimizeOption))
+ flags |= QQuickVectorImageGenerator::GeneratorFlag::OptimizePaths;
+ if (parser.isSet(outlineModeOption))
+ flags |= (QQuickVectorImageGenerator::GeneratorFlag::OutlineStrokeMode
+ | QQuickVectorImageGenerator::GeneratorFlag::OptimizePaths);
+
+ QQuickQmlGenerator generator(inFileName, flags, outFileName);
+ generator.setShapeTypeName(typeName);
+ generator.setCommentString(commentString);
+ generator.setAssetFileDirectory(assetOutputDirectory);
+ generator.setAssetFilePrefix(assetOutputPrefix);
+ generator.setRetainFilePaths(keepPaths);
+ bool ok = generator.generate();
+
+#ifdef ENABLE_GUI
+ if (ok && (parser.isSet(guiOption) || outFileName.isEmpty())) {
+ app.setOrganizationName("QtProject");
+ const QUrl url(QStringLiteral("qrc:/main.qml"));
+ QQmlApplicationEngine engine;
+ QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
+ &app, [&](QObject *obj, const QUrl &objUrl){
+ if (!obj && url == objUrl)
+ QCoreApplication::exit(-1);
+ if (obj) {
+ auto *containerItem = obj->findChild<QQuickItem*>(QStringLiteral("svg_item"));
+ QQuickItemGenerator generator(inFileName, flags, containerItem);
+ generator.generate();
+ }
+ });
+ engine.load(url);
+ return app.exec();
+ }
+#endif
+
+ return ok ? 0 : 1;
+}
diff --git a/tools/svgtoqml/main.qml b/tools/svgtoqml/main.qml
new file mode 100644
index 0000000000..9a8ceefe5d
--- /dev/null
+++ b/tools/svgtoqml/main.qml
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtQuick.Shapes
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Window {
+ id: mainWindow
+ width: 1280
+ height: 960
+ visible: true
+ color: "white"
+
+ Item {
+ id: svg
+ objectName: "svg_item"
+ anchors.centerIn: parent
+ }
+}