diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2020-09-02 12:34:29 +0200 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2020-11-24 15:05:13 +0100 |
commit | db0b7cfcb2923aa4f80ed4d2c2e4d2052d52f96a (patch) | |
tree | 477790e5ba9069123baaab4c7b63a5234fb05604 | |
parent | d2d8e90e9f218103d60737e1273ab5322834d9ec (diff) |
qmlformat: Add indent options
Adds the ability to use tabs or any amount of spaces for indentation
instead of the default of 4 spaces.
[ChangeLog][QML][qmlformat] Added option to customize
indentation.
Fixes: QTBUG-86413
Change-Id: I3c370dda2d0069ef61202a2d862ce15bc513e55e
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | tests/auto/qml/qmlformat/data/Example1.formatted.2spaces.qml | 151 | ||||
-rw-r--r-- | tests/auto/qml/qmlformat/data/Example1.formatted.tabs.qml | 151 | ||||
-rw-r--r-- | tests/auto/qml/qmlformat/tst_qmlformat.cpp | 80 | ||||
-rw-r--r-- | tools/qmlformat/dumpastvisitor.cpp | 11 | ||||
-rw-r--r-- | tools/qmlformat/dumpastvisitor.h | 8 | ||||
-rw-r--r-- | tools/qmlformat/main.cpp | 34 |
6 files changed, 384 insertions, 51 deletions
diff --git a/tests/auto/qml/qmlformat/data/Example1.formatted.2spaces.qml b/tests/auto/qml/qmlformat/data/Example1.formatted.2spaces.qml new file mode 100644 index 0000000000..20d2a03421 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/Example1.formatted.2spaces.qml @@ -0,0 +1,151 @@ +/* This file is licensed under the not a license license + 1. You may not comply + 2. Goodbye +*/ + +import A.B.B.A +import A.LLOHA +// Importing this is very important +import QtQuick 5.15 +// Muddling the waters! +import QtQuick.Models 3.14 as muddle +import That +import This // THIS IS VERY IMPORTANT! +import X.Y +import X.Z +import Y +// Importing that is important too +import Z + +// This comment is related to Item +Item { + // Orphan comment + // Another orphan + // More orphans + + // This to id + // Also id. (line 2) + // This is the third id + // fourth id comment + id: foo + + // This to enum + enum Foo { + A = 3, // This is A + B, // This is B + C = 4, // This is C + D // This is D + } + + property bool some_bool: false + property variant some_array_literal: [30, 20, Math["PI"], [4, 3, 2], "foo", 0.3] + property bool something_computed: function(x) { + // This is an orphan inside something_computed + // Are these getting duplicated? + // Another orphan inside something_computed + + const PI = 3, DAYS_PER_YEAR = 365.25; + var x = 3 + 2; + x["bla"] = 50; + // 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 { + dangerous(); + } + 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"); + } + return "foobar"; + }() + default property bool some_default_bool: 500 % 5 !== 0 // some_default_bool + // 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) + + // This one to aFunc() + function aFunc() { + var x = 3; + return x; + } + + x: 3 // Very cool + Component.onCompleted: console.log("Foo!") + myFavouriteThings: [ + // This is an orphan + + // This is a cool text + Text { + }, + // This is a cool rectangle + Rectangle { + } + ] + + Text { + required property string batman + + signal boo(int count, int times, real duration) + + text: "Bla" + } + + // This comment is related to the property animation + PropertyAnimation on x { + id: foo + + x: 3 + y: x + 3 + } + +} diff --git a/tests/auto/qml/qmlformat/data/Example1.formatted.tabs.qml b/tests/auto/qml/qmlformat/data/Example1.formatted.tabs.qml new file mode 100644 index 0000000000..d569c414d2 --- /dev/null +++ b/tests/auto/qml/qmlformat/data/Example1.formatted.tabs.qml @@ -0,0 +1,151 @@ +/* This file is licensed under the not a license license + 1. You may not comply + 2. Goodbye +*/ + +import A.B.B.A +import A.LLOHA +// Importing this is very important +import QtQuick 5.15 +// Muddling the waters! +import QtQuick.Models 3.14 as muddle +import That +import This // THIS IS VERY IMPORTANT! +import X.Y +import X.Z +import Y +// Importing that is important too +import Z + +// This comment is related to Item +Item { + // Orphan comment + // Another orphan + // More orphans + + // This to id + // Also id. (line 2) + // This is the third id + // fourth id comment + id: foo + + // This to enum + enum Foo { + A = 3, // This is A + B, // This is B + C = 4, // This is C + D // This is D + } + + property bool some_bool: false + property variant some_array_literal: [30, 20, Math["PI"], [4, 3, 2], "foo", 0.3] + property bool something_computed: function(x) { + // This is an orphan inside something_computed + // Are these getting duplicated? + // Another orphan inside something_computed + + const PI = 3, DAYS_PER_YEAR = 365.25; + var x = 3 + 2; + x["bla"] = 50; + // 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 { + dangerous(); + } + 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"); + } + return "foobar"; + }() + default property bool some_default_bool: 500 % 5 !== 0 // some_default_bool + // 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) + + // This one to aFunc() + function aFunc() { + var x = 3; + return x; + } + + x: 3 // Very cool + Component.onCompleted: console.log("Foo!") + myFavouriteThings: [ + // This is an orphan + + // This is a cool text + Text { + }, + // This is a cool rectangle + Rectangle { + } + ] + + Text { + required property string batman + + signal boo(int count, int times, real duration) + + text: "Bla" + } + + // This comment is related to the property animation + PropertyAnimation on x { + id: foo + + x: 3 + y: x + 3 + } + +} diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 9e91ff8569..f114704b0a 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -53,7 +53,7 @@ private Q_SLOTS: private: QString readTestFile(const QString &path); - QString runQmlformat(const QString &fileToFormat, bool sortImports, bool shouldSucceed, const QString &newlineFormat = "native"); + QString runQmlformat(const QString &fileToFormat, QStringList args, bool shouldSucceed = true); QString m_qmlformatPath; QStringList m_excludedDirs; @@ -179,16 +179,18 @@ QString TestQmlformat::readTestFile(const QString &path) void TestQmlformat::testLineEndings() { // macos - const QString macosContents = runQmlformat(testFile("Example1.formatted.qml"), false, true, "macos"); + const QString macosContents = + runQmlformat(testFile("Example1.formatted.qml"), { "-l", "macos" }); QVERIFY(!macosContents.contains("\n")); QVERIFY(macosContents.contains("\r")); // windows - const QString windowsContents = runQmlformat(testFile("Example1.formatted.qml"), false, true, "windows"); + const QString windowsContents = + runQmlformat(testFile("Example1.formatted.qml"), { "-l", "windows" }); QVERIFY(windowsContents.contains("\r\n")); // unix - const QString unixContents = runQmlformat(testFile("Example1.formatted.qml"), false, true, "unix"); + const QString unixContents = runQmlformat(testFile("Example1.formatted.qml"), { "-l", "unix" }); QVERIFY(unixContents.contains("\n")); QVERIFY(!unixContents.contains("\r")); } @@ -197,58 +199,63 @@ void TestQmlformat::testFormat_data() { QTest::addColumn<QString>("file"); QTest::addColumn<QString>("fileFormatted"); - QTest::addColumn<bool>("sortImports"); - QTest::addColumn<bool>("shouldSucceed"); + QTest::addColumn<QStringList>("args"); QTest::newRow("example1 (sorted)") << "Example1.qml" - << "Example1.formatted.qml" << true << true; - QTest::newRow("example1 (not sorted)") << "Example1.qml" - << "Example1.formatted.nosort.qml" << false << true; + << "Example1.formatted.qml" << QStringList {}; + QTest::newRow("example1 (not sorted)") + << "Example1.qml" + << "Example1.formatted.nosort.qml" << QStringList { "-n" }; + QTest::newRow("example1 (tabs)") << "Example1.qml" + << "Example1.formatted.tabs.qml" << QStringList { "-t" }; + QTest::newRow("example1 (two spaces)") + << "Example1.qml" + << "Example1.formatted.2spaces.qml" << QStringList { "-w", "2" }; QTest::newRow("annotation (sorted)") << "Annotations.qml" - << "Annotations.formatted.qml" << true << true; - QTest::newRow("annotation (not sorted)") << "Annotations.qml" - << "Annotations.formatted.nosort.qml" << false << true; + << "Annotations.formatted.qml" << QStringList {}; + QTest::newRow("annotation (not sorted)") + << "Annotations.qml" + << "Annotations.formatted.nosort.qml" << QStringList { "-n" }; QTest::newRow("front inline") << "FrontInline.qml" - << "FrontInline.formatted.qml" << false << true; + << "FrontInline.formatted.qml" << QStringList {}; QTest::newRow("if blocks") << "IfBlocks.qml" - << "IfBlocks.formatted.qml" << false << true; + << "IfBlocks.formatted.qml" << QStringList {}; QTest::newRow("read-only properties") << "readOnlyProps.qml" - << "readOnlyProps.formatted.qml" << false << true; + << "readOnlyProps.formatted.qml" << QStringList {}; QTest::newRow("states and transitions") << "statesAndTransitions.qml" - << "statesAndTransitions.formatted.qml" << false << true; + << "statesAndTransitions.formatted.qml" << QStringList {}; QTest::newRow("large bindings") << "largeBindings.qml" - << "largeBindings.formatted.qml" << false << true; + << "largeBindings.formatted.qml" << QStringList {}; QTest::newRow("verbatim strings") << "verbatimString.qml" - << "verbatimString.formatted.qml" << false << true; + << "verbatimString.formatted.qml" << QStringList {}; QTest::newRow("inline components") << "inlineComponents.qml" - << "inlineComponents.formatted.qml" << false << true; + << "inlineComponents.formatted.qml" << QStringList {}; QTest::newRow("nested ifs") << "nestedIf.qml" - << "nestedIf.formatted.qml" << false << true; + << "nestedIf.formatted.qml" << QStringList {}; QTest::newRow("QTBUG-85003") << "QtBug85003.qml" - << "QtBug85003.formatted.qml" << false << true; + << "QtBug85003.formatted.qml" << QStringList {}; QTest::newRow("nested functions") << "nestedFunctions.qml" - << "nestedFunctions.formatted.qml" << false << true; + << "nestedFunctions.formatted.qml" << QStringList {}; QTest::newRow("multiline comments") << "multilineComment.qml" - << "multilineComment.formatted.qml" << false << true; + << "multilineComment.formatted.qml" << QStringList {}; QTest::newRow("for of") << "forOf.qml" - << "forOf.formatted.qml" << false << true; + << "forOf.formatted.qml" << QStringList {}; QTest::newRow("property names") << "propertyNames.qml" - << "propertyNames.formatted.qml" << false << true; + << "propertyNames.formatted.qml" << QStringList {}; QTest::newRow("empty object") << "emptyObject.qml" - << "emptyObject.formatted.qml" << false << true; + << "emptyObject.formatted.qml" << QStringList {}; QTest::newRow("arrow functions") << "arrowFunctions.qml" - << "arrowFunctions.formatted.qml" << false << true; + << "arrowFunctions.formatted.qml" << QStringList {}; } void TestQmlformat::testFormat() { QFETCH(QString, file); QFETCH(QString, fileFormatted); - QFETCH(bool, sortImports); - QFETCH(bool, shouldSucceed); + QFETCH(QStringList, args); - QCOMPARE(runQmlformat(testFile(file), sortImports, shouldSucceed), readTestFile(fileFormatted)); + QCOMPARE(runQmlformat(testFile(file), args), readTestFile(fileFormatted)); } #if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled @@ -273,29 +280,24 @@ void TestQmlformat::testExample() { QFETCH(QString, file); const bool isInvalid = isInvalidFile(QFileInfo(file)); - QString output = runQmlformat(file, true, !isInvalid); + QString output = runQmlformat(file, {}, !isInvalid); if (!isInvalid) QVERIFY(!output.isEmpty()); } #endif -QString TestQmlformat::runQmlformat(const QString &fileToFormat, bool sortImports, bool shouldSucceed, const QString &newlineFormat) +QString TestQmlformat::runQmlformat(const QString &fileToFormat, QStringList args, + bool shouldSucceed) { // Copy test file to temporary location QTemporaryDir tempDir; const QString tempFile = tempDir.path() + QDir::separator() + "to_format.qml"; QFile::copy(fileToFormat, tempFile); - QStringList args; - args << "-i"; + args << QLatin1String("-i"); args << tempFile; - if (!sortImports) - args << "-n"; - - args << "-l" << newlineFormat; - auto verify = [&]() { QProcess process; process.start(m_qmlformatPath, args); diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp index 24286ba69b..771814ffd5 100644 --- a/tools/qmlformat/dumpastvisitor.cpp +++ b/tools/qmlformat/dumpastvisitor.cpp @@ -30,8 +30,9 @@ #include <QtQml/private/qqmljslexer_p.h> -DumpAstVisitor::DumpAstVisitor(QQmlJS::Engine *engine, Node *rootNode, CommentAstVisitor *comment) - : m_engine(engine), m_comment(comment) +DumpAstVisitor::DumpAstVisitor(QQmlJS::Engine *engine, Node *rootNode, CommentAstVisitor *comment, + int indentWidth, DumpAstVisitor::Indentation indentation) + : m_engine(engine), m_comment(comment), m_indentWidth(indentWidth), m_indentation(indentation) { // Add all completely orphaned comments m_result += getOrphanedComments(nullptr); @@ -992,11 +993,9 @@ bool DumpAstVisitor::visit(UiPublicMember *node) { return true; } -static QString generateIndent(int indentLevel) +QString DumpAstVisitor::generateIndent(int indentLevel) const { - constexpr int IDENT_WIDTH = 4; - - return QString(IDENT_WIDTH * indentLevel, ' '); + return QString(m_indentWidth * indentLevel, m_indentation == Indentation::Tabs ? '\t' : ' '); } QString DumpAstVisitor::formatLine(QString line, bool newline) const diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h index 729e15702a..657592f403 100644 --- a/tools/qmlformat/dumpastvisitor.h +++ b/tools/qmlformat/dumpastvisitor.h @@ -43,7 +43,10 @@ using namespace QQmlJS; class DumpAstVisitor : protected Visitor { public: - DumpAstVisitor(QQmlJS::Engine *engine, Node *rootNode, CommentAstVisitor *comment); + enum Indentation { Tabs, Spaces }; + + DumpAstVisitor(QQmlJS::Engine *engine, Node *rootNode, CommentAstVisitor *comment, + int indentWidth, Indentation indentation); QString toString() const { return m_result; } @@ -94,6 +97,7 @@ private: QHash<QString, UiObjectMember*> m_bindings; }; + QString generateIndent(int indentLevel) const; QString formatLine(QString line, bool newline = true) const; QString formatPartlyFormatedLines(QString line, bool newline = true) const; @@ -150,6 +154,8 @@ private: QString m_component_name = ""; QQmlJS::Engine *m_engine; CommentAstVisitor *m_comment; + int m_indentWidth; + Indentation m_indentation; }; #endif // DUMPAST_H diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp index f9afd10a2c..a021971710 100644 --- a/tools/qmlformat/main.cpp +++ b/tools/qmlformat/main.cpp @@ -44,7 +44,8 @@ #include "dumpastvisitor.h" #include "restructureastvisitor.h" -bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImports, bool force, const QString& newline) +bool parseFile(const QString &filename, bool inplace, bool verbose, bool sortImports, bool force, + int indentWidth, bool tabs, const QString &newline) { QFile file(filename); @@ -100,7 +101,9 @@ bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImp if (verbose) qWarning().noquote() << "Dumping" << filename; - DumpAstVisitor dump(&engine, parser.rootNode(), &comment); + DumpAstVisitor dump(&engine, parser.rootNode(), &comment, tabs ? 1 : indentWidth, + tabs ? DumpAstVisitor::Indentation::Tabs + : DumpAstVisitor::Indentation::Spaces); QString dumpCode = dump.toString(); @@ -191,6 +194,13 @@ int main(int argc, char *argv[]) parser.addOption(QCommandLineOption({"f", "force"}, QStringLiteral("Continue even if an error has occurred."))); + parser.addOption( + QCommandLineOption({ "t", "tabs" }, QStringLiteral("Use tabs instead of spaces."))); + + parser.addOption(QCommandLineOption({ "w", "indent-width" }, + QStringLiteral("How many spaces are used when indenting."), + "width", "4")); + parser.addOption(QCommandLineOption( { "F", "files" }, QStringLiteral("Format all files listed in file, in-place"), "file")); @@ -212,6 +222,19 @@ int main(int argc, char *argv[]) return -1; } + if (parser.isSet("indent-width") && parser.isSet("tabs")) { + qWarning() << "Error: Cannot use --indent-width with --tabs"; + return -1; + } + + bool indentWidthOkay = false; + int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay); + + if (!indentWidthOkay) { + qWarning() << "Error: Invalid value passed to -w"; + return -1; + } + if (parser.isSet("files")) { if (!positionalArguments.isEmpty()) qWarning() << "Warning: Positional arguments are ignored when -F is used"; @@ -226,14 +249,15 @@ int main(int argc, char *argv[]) continue; if (!parseFile(file, true, parser.isSet("verbose"), !parser.isSet("no-sort"), - parser.isSet("force"), parser.value("newline"))) + parser.isSet("force"), indentWidth, parser.isSet("tabs"), + parser.value("newline"))) success = false; } } else { for (const QString &file : parser.positionalArguments()) { if (!parseFile(file, parser.isSet("inplace"), parser.isSet("verbose"), - !parser.isSet("no-sort"), parser.isSet("force"), - parser.value("newline"))) + !parser.isSet("no-sort"), parser.isSet("force"), indentWidth, + parser.isSet("tabs"), parser.value("newline"))) success = false; } } |