aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmlformat/qmlformat.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qmlformat/qmlformat.cpp')
-rw-r--r--tools/qmlformat/qmlformat.cpp279
1 files changed, 279 insertions, 0 deletions
diff --git a/tools/qmlformat/qmlformat.cpp b/tools/qmlformat/qmlformat.cpp
new file mode 100644
index 0000000000..7f7cdfbb24
--- /dev/null
+++ b/tools/qmlformat/qmlformat.cpp
@@ -0,0 +1,279 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <QFile>
+#include <QTextStream>
+
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQmlDom/private/qqmldomitem_p.h>
+#include <QtQmlDom/private/qqmldomexternalitems_p.h>
+#include <QtQmlDom/private/qqmldomtop_p.h>
+#include <QtQmlDom/private/qqmldomoutwriter_p.h>
+
+#if QT_CONFIG(commandlineparser)
+# include <QCommandLineParser>
+#endif
+
+using namespace QQmlJS::Dom;
+
+struct Options
+{
+ bool verbose = false;
+ bool inplace = false;
+ bool force = false;
+ bool tabs = false;
+ bool valid = false;
+ bool normalize = false;
+
+ int indentWidth = 4;
+ bool indentWidthSet = false;
+ QString newline = "native";
+
+ QStringList files;
+ QStringList arguments;
+ QStringList errors;
+};
+
+bool parseFile(const QString &filename, const Options &options)
+{
+ 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) {
+ if (crIndex >= 0) {
+ if (crIndex + 1 == newlineIndex)
+ lwOptions.lineEndings = LineWriterOptions::LineEndings::Windows;
+ else
+ qWarning().noquote() << "Invalid line ending in file, using default";
+
+ } else {
+ lwOptions.lineEndings = LineWriterOptions::LineEndings::Unix;
+ }
+ } else if (crIndex >= 0) {
+ lwOptions.lineEndings = LineWriterOptions::LineEndings::OldMacOs;
+ } else {
+ qWarning().noquote() << "Unknown line ending in file, using default";
+ }
+ } else if (options.newline == "macos") {
+ lwOptions.lineEndings = LineWriterOptions::LineEndings::OldMacOs;
+ } else if (options.newline == "windows") {
+ lwOptions.lineEndings = LineWriterOptions::LineEndings::Windows;
+ } else if (options.newline == "unix") {
+ lwOptions.lineEndings = LineWriterOptions::LineEndings::Unix;
+ } else {
+ qWarning().noquote() << "Unknown line ending type" << options.newline;
+ return false;
+ }
+
+ 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;
+
+ MutableDomItem res;
+ if (options.inplace) {
+ if (options.verbose)
+ qWarning().noquote() << "Writing to file" << filename;
+ FileWriter fw;
+ res = qmlFile.writeOut(filename, 2, 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();
+ }
+ return bool(res);
+}
+
+Options buildCommandLineOptions(const QCoreApplication &app)
+{
+#if QT_CONFIG(commandlineparser)
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Formats QML files according to the QML Coding Conventions.");
+ parser.addHelpOption();
+ parser.addVersionOption();
+
+ parser.addOption(
+ QCommandLineOption({ "V", "verbose" },
+ QStringLiteral("Verbose mode. Outputs more detailed information.")));
+
+ parser.addOption(QCommandLineOption(
+ { "i", "inplace" },
+ QStringLiteral("Edit file in-place instead of outputting to stdout.")));
+
+ 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({ "n", "normalize" },
+ QStringLiteral("Reorders the attributes of the objects "
+ "according to the QML Coding Guidelines.")));
+
+ parser.addOption(QCommandLineOption(
+ { "F", "files" }, QStringLiteral("Format all files listed in file, in-place"), "file"));
+
+ parser.addOption(QCommandLineOption(
+ { "l", "newline" },
+ QStringLiteral("Override the new line format to use (native macos unix windows)."),
+ "newline", "native"));
+
+ parser.addPositionalArgument("filenames", "files to be processed by qmlformat");
+
+ parser.process(app);
+
+ bool indentWidthOkay = false;
+ const int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay);
+ if (!indentWidthOkay) {
+ Options options;
+ options.errors.push_back("Error: Invalid value passed to -w");
+ return options;
+ }
+
+ QStringList files;
+ if (!parser.value("files").isEmpty()) {
+ QFile file(parser.value("files"));
+ file.open(QIODevice::Text | QIODevice::ReadOnly);
+ if (file.isOpen()) {
+ QTextStream in(&file);
+ while (!in.atEnd()) {
+ QString file = in.readLine();
+
+ if (file.isEmpty())
+ continue;
+
+ files.push_back(file);
+ }
+ }
+ }
+
+ Options options;
+ options.verbose = parser.isSet("verbose");
+ options.inplace = parser.isSet("inplace");
+ options.force = parser.isSet("force");
+ options.tabs = parser.isSet("tabs");
+ options.normalize = parser.isSet("normalize");
+ options.valid = true;
+
+ options.indentWidth = indentWidth;
+ options.indentWidthSet = parser.isSet("indent-width");
+ options.newline = parser.value("newline");
+ options.files = files;
+ options.arguments = parser.positionalArguments();
+ return options;
+#else
+ return Options {};
+#endif
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ QCoreApplication::setApplicationName("qmlformat");
+ QCoreApplication::setApplicationVersion("1.0");
+
+ const auto options = buildCommandLineOptions(app);
+ if (!options.valid) {
+ for (const auto &error : options.errors) {
+ qWarning().noquote() << error;
+ }
+
+ return -1;
+ }
+
+ bool success = true;
+ if (!options.files.isEmpty()) {
+ if (!options.arguments.isEmpty())
+ qWarning() << "Warning: Positional arguments are ignored when -F is used";
+
+ for (const QString &file : options.files) {
+ Q_ASSERT(!file.isEmpty());
+
+ if (!parseFile(file, options))
+ success = false;
+ }
+ } else {
+ for (const QString &file : options.arguments) {
+ if (!parseFile(file, options))
+ success = false;
+ }
+ }
+
+ return success ? 0 : 1;
+}