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 <fabian.kosmale@qt.io>
This commit is contained in:
Maximilian Goldstein 2021-10-25 18:30:26 +02:00
parent e2532e8773
commit e14d13bea6
7 changed files with 339 additions and 2 deletions

View File

@ -111,4 +111,7 @@ by specifying the \c{-n} flag.
By default, qmlformat writes the formatted version of the file to stdout. 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. 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.
*/ */

View File

@ -0,0 +1,5 @@
[General]
IndentWidth=2
NewlineType=macos
NormalizeOrder=true
UseTabs=

View File

@ -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!")
}

View File

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

View File

@ -252,6 +252,8 @@ void TestQmlformat::testFormat_data()
<< "emptyObject.formatted.qml" << QStringList {}; << "emptyObject.formatted.qml" << QStringList {};
QTest::newRow("arrow functions") << "arrowFunctions.qml" QTest::newRow("arrow functions") << "arrowFunctions.qml"
<< "arrowFunctions.formatted.qml" << QStringList {}; << "arrowFunctions.formatted.qml" << QStringList {};
QTest::newRow("settings") << "settings/Example1.qml"
<< "settings/Example1.formatted.qml" << QStringList {};
} }
void TestQmlformat::testFormat() void TestQmlformat::testFormat()

View File

@ -10,6 +10,8 @@ qt_internal_add_tool(${target_name}
TOOLS_TARGET Qml # special case TOOLS_TARGET Qml # special case
SOURCES SOURCES
qmlformat.cpp qmlformat.cpp
../shared/qqmltoolingsettings.h
../shared/qqmltoolingsettings.cpp
PUBLIC_LIBRARIES PUBLIC_LIBRARIES
Qt::Core Qt::Core
Qt::QmlDomPrivate Qt::QmlDomPrivate

View File

@ -44,6 +44,8 @@
# include <QCommandLineParser> # include <QCommandLineParser>
#endif #endif
#include "../shared/qqmltoolingsettings.h"
using namespace QQmlJS::Dom; using namespace QQmlJS::Dom;
struct Options struct Options
@ -54,6 +56,8 @@ struct Options
bool tabs = false; bool tabs = false;
bool valid = false; bool valid = false;
bool normalize = false; bool normalize = false;
bool ignoreSettings = false;
bool writeDefaultSettings = false;
int indentWidth = 4; int indentWidth = 4;
bool indentWidthSet = false; bool indentWidthSet = false;
@ -168,6 +172,17 @@ Options buildCommandLineOptions(const QCoreApplication &app)
QCommandLineOption({ "V", "verbose" }, QCommandLineOption({ "V", "verbose" },
QStringLiteral("Verbose mode. Outputs more detailed information."))); 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( parser.addOption(QCommandLineOption(
{ "i", "inplace" }, { "i", "inplace" },
QStringLiteral("Edit file in-place instead of outputting to stdout."))); QStringLiteral("Edit file in-place instead of outputting to stdout.")));
@ -198,6 +213,13 @@ Options buildCommandLineOptions(const QCoreApplication &app)
parser.process(app); parser.process(app);
if (parser.isSet(writeDefaultsOption)) {
Options options;
options.writeDefaultSettings = true;
options.valid = true;
return options;
}
bool indentWidthOkay = false; bool indentWidthOkay = false;
const int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay); const int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay);
if (!indentWidthOkay) { if (!indentWidthOkay) {
@ -229,6 +251,7 @@ Options buildCommandLineOptions(const QCoreApplication &app)
options.force = parser.isSet("force"); options.force = parser.isSet("force");
options.tabs = parser.isSet("tabs"); options.tabs = parser.isSet("tabs");
options.normalize = parser.isSet("normalize"); options.normalize = parser.isSet("normalize");
options.ignoreSettings = parser.isSet("ignore-settings");
options.valid = true; options.valid = true;
options.indentWidth = indentWidth; options.indentWidth = indentWidth;
@ -248,6 +271,20 @@ int main(int argc, char *argv[])
QCoreApplication::setApplicationName("qmlformat"); QCoreApplication::setApplicationName("qmlformat");
QCoreApplication::setApplicationVersion(QT_VERSION_STR); 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); const auto options = buildCommandLineOptions(app);
if (!options.valid) { if (!options.valid) {
for (const auto &error : options.errors) { for (const auto &error : options.errors) {
@ -257,6 +294,32 @@ int main(int argc, char *argv[])
return -1; 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; bool success = true;
if (!options.files.isEmpty()) { if (!options.files.isEmpty()) {
if (!options.arguments.isEmpty()) if (!options.arguments.isEmpty())
@ -265,12 +328,12 @@ int main(int argc, char *argv[])
for (const QString &file : options.files) { for (const QString &file : options.files) {
Q_ASSERT(!file.isEmpty()); Q_ASSERT(!file.isEmpty());
if (!parseFile(file, options)) if (!parseFile(file, getSettings(file, options)))
success = false; success = false;
} }
} else { } else {
for (const QString &file : options.arguments) { for (const QString &file : options.arguments) {
if (!parseFile(file, options)) if (!parseFile(file, getSettings(file, options)))
success = false; success = false;
} }
} }