From e14d13bea68a5bf40a5475e5065651f750d4af04 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Mon, 25 Oct 2021 18:30:26 +0200 Subject: qmlformat: Implement settings file Implements controlling qmlformat via a settings file as can be done for qmllint. [ChangeLog][General][qmlformat] Adds the ability to set linting options via a settings file rather than using command line parameters. Use --write-defaults to generate a template with default values for editing. Use --ignore-settings to disable this feature. Fixes: QTBUG-86415 Change-Id: I282c3b994ca6cc491a27b45f531f1ba1c2652ef7 Reviewed-by: Fabian Kosmale --- .../src/guidelines/qtquick-toolsnutilities.qdoc | 3 + .../qml/qmlformat/data/settings/.qmlformat.ini | 5 + .../qmlformat/data/settings/Example1.formatted.qml | 157 +++++++++++++++++++++ .../auto/qml/qmlformat/data/settings/Example1.qml | 105 ++++++++++++++ tests/auto/qml/qmlformat/tst_qmlformat.cpp | 2 + tools/qmlformat/CMakeLists.txt | 2 + tools/qmlformat/qmlformat.cpp | 67 ++++++++- 7 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 tests/auto/qml/qmlformat/data/settings/.qmlformat.ini create mode 100644 tests/auto/qml/qmlformat/data/settings/Example1.formatted.qml create mode 100644 tests/auto/qml/qmlformat/data/settings/Example1.qml diff --git a/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc b/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc index 96d2163eda..266cbf064c 100644 --- a/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc +++ b/src/quick/doc/src/guidelines/qtquick-toolsnutilities.qdoc @@ -111,4 +111,7 @@ by specifying the \c{-n} flag. By default, qmlformat writes the formatted version of the file to stdout. If you wish to have your file updated in-place specify the \c{-i} flag. + +You may also change tab widths and line ending types among other settings, +either via command line options or by using a settings file. */ diff --git a/tests/auto/qml/qmlformat/data/settings/.qmlformat.ini b/tests/auto/qml/qmlformat/data/settings/.qmlformat.ini new file mode 100644 index 0000000000..ae1eed5254 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/settings/.qmlformat.ini @@ -0,0 +1,5 @@ +[General] +IndentWidth=2 +NewlineType=macos +NormalizeOrder=true +UseTabs= diff --git a/tests/auto/qml/qmlformat/data/settings/Example1.formatted.qml b/tests/auto/qml/qmlformat/data/settings/Example1.formatted.qml new file mode 100644 index 0000000000..a679e8a010 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/settings/Example1.formatted.qml @@ -0,0 +1,157 @@ +/* This file is licensed under the not a license license + 1. You may not comply + 2. Goodbye +*/ + +// Importing this is very important +import QtQuick 5.15 +// Muddling the waters! +import QtQuick.Models 3.14 as muddle +// Importing that is important too +import Z +import That +import This // THIS IS VERY IMPORTANT! +import Y +import X.Z +import X.Y +import A.LLOHA +import A.B.B.A + +// This comment is related to Item +Item { + + // This to id + // Also id. (line 2) + // This is the third id + // fourth id comment + id: foo + x: 3 // Very cool + + // This to enum + enum Foo { + A = 3, // This is A + B, // This is B + C = 4, // This is C + D // This is D + } + + // This one to aFunc() + function aFunc() { + var x = 3; + return x; + } + + property bool some_bool: false + // This comment is related to the property animation + PropertyAnimation on x { + id: foo2 + x: 3 + y: x + 3 + } + + // Orphan comment + + // Another orphan + + // More orphans + property variant some_array_literal: [30, 20, Math["PI"], [4, 3, 2], "foo", 0.3] + + property bool something_computed: function (x) { + const PI = 3, DAYS_PER_YEAR = 365.25; + var x = 3 + 2; + x["bla"] = 50; + + // This is an orphan inside something_computed + + // Are these getting duplicated? + + // This one to var few! + var few = new WhatEver(); + x += Math.sin(3); + x--; + --x; + x++; + ++x; + for (var x = 0; x < 100; x++) { + x++; + console.log("Foo"); + } + for (var x in [3, 2, 1]) { + y++; + console.log("Bar"); + } + while (true) { + console.log("Wee"); + } + with (foo) { + bar; + x += 5; + } // This is related to with! + x3: do { + console.log("Hello"); + } while (3 == 0) + try { + dangerous(); + } catch (e) { + console.log(e); + } finally { + console.log("What else?"); + } + switch (x) { + case 0: + x = 1; + break; + case 1: + x = 5; + break; + case 4: + x = 100; + break; + } + if (x == 50) { + console.log("true"); + } else if (x == 50) { + console.log("other thing"); + } else { + console.log("false"); + } + if (x == 50) { + console.log("true"); + } else if (x == 50) { + console.log("other thing"); + x--; + } else { + console.log("false"); + } + + // Another orphan inside something_computed + return "foobar"; + }() + + default property bool some_default_bool: 500 % 5 !== 0 // some_default_bool + myFavouriteThings: [ + // This is an orphan + + // This is a cool text + Text { + }, + // This is a cool rectangle + Rectangle { + } + ] + + // some_read_only_bool + readonly property bool some_read_only_bool: Math.sin(3) && (aFunc()[30] + 5) | 2 != 0 + + signal say(string name, bool caps) + + Text { + text: "Bla" + + signal boo(int count, int times, real duration) + + required property string batman + } + + Component.onCompleted: console.log("Foo!") +} diff --git a/tests/auto/qml/qmlformat/data/settings/Example1.qml b/tests/auto/qml/qmlformat/data/settings/Example1.qml new file mode 100644 index 0000000000..36ffc6b058 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/settings/Example1.qml @@ -0,0 +1,105 @@ + + +/* This file is licensed under the not a license license + 1. You may not comply + 2. Goodbye +*/ + +// Importing this is very important +import QtQuick 5.15 +// Muddling the waters! +import QtQuick.Models 3.14 as muddle +// Importing that is important too +import Z +import That +import This // THIS IS VERY IMPORTANT! +import Y +import X.Z +import X.Y +import A.LLOHA +import A.B.B.A + +// This comment is related to Item +Item { + x: 3 // Very cool + + // This to enum + enum Foo { + A = 3, // This is A + B, // This is B + C = 4, // This is C + D // This is D + } + + // This one to aFunc() + function aFunc() { + var x = 3; + return x; + } + + property bool some_bool : false + // This comment is related to the property animation + PropertyAnimation on x { + id: foo2; x: 3; y: x + 3 + } + + // Orphan comment + + // Another orphan + + // More orphans + + + property variant some_array_literal: [30,20,Math["PI"],[4,3,2],"foo",0.3] + property bool something_computed: function(x) { + const PI = 3, DAYS_PER_YEAR=365.25; var x = 3 + 2; x["bla"] = 50; + + // This is an orphan inside something_computed + + // Are these getting duplicated? + + + // This one to var few! + var few = new WhatEver(); + x += Math.sin(3); x--; --x; x++; ++x; + for (var x = 0; x < 100; x++) { x++; console.log("Foo"); } + for (var x in [3,2,1]) { y++; console.log("Bar"); } + while (true) { console.log("Wee"); } + with (foo) { bar; x+=5; } // This is related to with! + x3: + do { console.log("Hello"); } while (3 == 0) + try { dangerous(); } catch(e) { console.log(e); } finally { console.log("What else?"); } + switch (x) { case 0: x = 1; break; case 1: x = 5; break; case 4: x = 100; break; } + if (x == 50) { console.log("true"); } else if (x == 50) { console.log("other thing"); } else { console.log("false"); } + if (x == 50) { console.log("true"); } else if (x == 50) { console.log("other thing"); x--; } else { console.log("false"); } + + // Another orphan inside something_computed + + return "foobar"; }(); + + default property bool some_default_bool : 500 % 5 !== 0 // some_default_bool + + myFavouriteThings: [ + // This is an orphan + + // This is a cool text + Text {}, + // This is a cool rectangle + Rectangle {}] + + // some_read_only_bool + readonly property bool some_read_only_bool : Math.sin(3) && (aFunc()[30] + 5) | 2 != 0 + + signal say(string name, bool caps); + + Text { text: "Bla"; signal boo(int count, int times, real duration); required property string batman; } + + Component.onCompleted: console.log("Foo!"); + + // This to id + // Also id. (line 2) + // This is the third id + // fourth id comment + id: foo + +} diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 65b423b919..e018c4828a 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -252,6 +252,8 @@ void TestQmlformat::testFormat_data() << "emptyObject.formatted.qml" << QStringList {}; QTest::newRow("arrow functions") << "arrowFunctions.qml" << "arrowFunctions.formatted.qml" << QStringList {}; + QTest::newRow("settings") << "settings/Example1.qml" + << "settings/Example1.formatted.qml" << QStringList {}; } void TestQmlformat::testFormat() diff --git a/tools/qmlformat/CMakeLists.txt b/tools/qmlformat/CMakeLists.txt index 232aefcfbf..0ffe698cf0 100644 --- a/tools/qmlformat/CMakeLists.txt +++ b/tools/qmlformat/CMakeLists.txt @@ -10,6 +10,8 @@ qt_internal_add_tool(${target_name} TOOLS_TARGET Qml # special case SOURCES qmlformat.cpp + ../shared/qqmltoolingsettings.h + ../shared/qqmltoolingsettings.cpp PUBLIC_LIBRARIES Qt::Core Qt::QmlDomPrivate diff --git a/tools/qmlformat/qmlformat.cpp b/tools/qmlformat/qmlformat.cpp index dab1ee42c7..75120b3ab0 100644 --- a/tools/qmlformat/qmlformat.cpp +++ b/tools/qmlformat/qmlformat.cpp @@ -44,6 +44,8 @@ # include #endif +#include "../shared/qqmltoolingsettings.h" + using namespace QQmlJS::Dom; struct Options @@ -54,6 +56,8 @@ struct Options bool tabs = false; bool valid = false; bool normalize = false; + bool ignoreSettings = false; + bool writeDefaultSettings = false; int indentWidth = 4; bool indentWidthSet = false; @@ -168,6 +172,17 @@ Options buildCommandLineOptions(const QCoreApplication &app) QCommandLineOption({ "V", "verbose" }, QStringLiteral("Verbose mode. Outputs more detailed information."))); + QCommandLineOption writeDefaultsOption( + QStringList() << "write-defaults", + QLatin1String("Writes defaults settings to .qmlformat.ini and exits (Warning: This " + "will overwrite any existing settings and comments!)")); + parser.addOption(writeDefaultsOption); + + QCommandLineOption ignoreSettings(QStringList() << "ignore-settings", + QLatin1String("Ignores all settings files and only takes " + "command line options into consideration")); + parser.addOption(ignoreSettings); + parser.addOption(QCommandLineOption( { "i", "inplace" }, QStringLiteral("Edit file in-place instead of outputting to stdout."))); @@ -198,6 +213,13 @@ Options buildCommandLineOptions(const QCoreApplication &app) parser.process(app); + if (parser.isSet(writeDefaultsOption)) { + Options options; + options.writeDefaultSettings = true; + options.valid = true; + return options; + } + bool indentWidthOkay = false; const int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay); if (!indentWidthOkay) { @@ -229,6 +251,7 @@ Options buildCommandLineOptions(const QCoreApplication &app) options.force = parser.isSet("force"); options.tabs = parser.isSet("tabs"); options.normalize = parser.isSet("normalize"); + options.ignoreSettings = parser.isSet("ignore-settings"); options.valid = true; options.indentWidth = indentWidth; @@ -248,6 +271,20 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationName("qmlformat"); QCoreApplication::setApplicationVersion(QT_VERSION_STR); + QQmlToolingSettings settings(QLatin1String("qmlformat")); + + const QString &useTabsSetting = QStringLiteral("UseTabs"); + settings.addOption(useTabsSetting); + + const QString &indentWidthSetting = QStringLiteral("IndentWidth"); + settings.addOption(indentWidthSetting, 4); + + const QString &normalizeSetting = QStringLiteral("NormalizeOrder"); + settings.addOption(normalizeSetting); + + const QString &newlineSetting = QStringLiteral("NewlineType"); + settings.addOption(newlineSetting, QStringLiteral("native")); + const auto options = buildCommandLineOptions(app); if (!options.valid) { for (const auto &error : options.errors) { @@ -257,6 +294,32 @@ int main(int argc, char *argv[]) return -1; } + if (options.writeDefaultSettings) + return settings.writeDefaults() ? 0 : -1; + + auto getSettings = [&](const QString &file, Options options) { + if (options.ignoreSettings || !settings.search(file)) + return options; + + Options perFileOptions = options; + + // Allow for tab settings to be overwritten by the command line + if (!options.indentWidthSet) { + if (settings.isSet(indentWidthSetting)) + perFileOptions.indentWidth = settings.value(indentWidthSetting).toInt(); + if (settings.isSet(useTabsSetting)) + perFileOptions.tabs = settings.value(useTabsSetting).toBool(); + } + + if (settings.isSet(normalizeSetting)) + perFileOptions.normalize = settings.value(normalizeSetting).toBool(); + + if (settings.isSet(newlineSetting)) + perFileOptions.newline = settings.value(newlineSetting).toString(); + + return perFileOptions; + }; + bool success = true; if (!options.files.isEmpty()) { if (!options.arguments.isEmpty()) @@ -265,12 +328,12 @@ int main(int argc, char *argv[]) for (const QString &file : options.files) { Q_ASSERT(!file.isEmpty()); - if (!parseFile(file, options)) + if (!parseFile(file, getSettings(file, options))) success = false; } } else { for (const QString &file : options.arguments) { - if (!parseFile(file, options)) + if (!parseFile(file, getSettings(file, options))) success = false; } } -- cgit v1.2.3