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.
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 {};
QTest::newRow("arrow functions") << "arrowFunctions.qml"
<< "arrowFunctions.formatted.qml" << QStringList {};
QTest::newRow("settings") << "settings/Example1.qml"
<< "settings/Example1.formatted.qml" << QStringList {};
}
void TestQmlformat::testFormat()

View File

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

View File

@ -44,6 +44,8 @@
# include <QCommandLineParser>
#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;
}
}