2024-09-13 17:18:28 +00:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
|
|
|
|
#include "qqmlformatoptions_p.h"
|
2025-01-24 12:51:04 +00:00
|
|
|
#include "qqmlformatsettings_p.h"
|
2024-09-13 17:18:28 +00:00
|
|
|
|
2025-01-24 12:23:35 +00:00
|
|
|
#include <QCommandLineParser>
|
|
|
|
#include <QCommandLineOption>
|
2025-01-27 11:06:07 +00:00
|
|
|
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2024-09-13 17:18:28 +00:00
|
|
|
QQmlFormatOptions::QQmlFormatOptions()
|
|
|
|
{
|
|
|
|
setTabsEnabled(false);
|
|
|
|
setNormalizeEnabled(false);
|
|
|
|
setObjectsSpacing(false);
|
|
|
|
setFunctionsSpacing(false);
|
|
|
|
setIndentWidth(4);
|
|
|
|
}
|
|
|
|
|
|
|
|
QQmlFormatOptions::LineEndings QQmlFormatOptions::detectLineEndings(const QString &code)
|
|
|
|
{
|
|
|
|
const QQmlJS::Dom::LineWriterOptions::LineEndings defaultEndings =
|
|
|
|
#if defined(Q_OS_WIN)
|
|
|
|
LineEndings::Windows;
|
|
|
|
#else
|
|
|
|
LineEndings::Unix;
|
|
|
|
#endif
|
|
|
|
// find out current line endings...
|
|
|
|
int newlineIndex = code.indexOf(QChar(u'\n'));
|
|
|
|
int crIndex = code.indexOf(QChar(u'\r'));
|
|
|
|
if (newlineIndex >= 0) {
|
|
|
|
if (crIndex >= 0) {
|
|
|
|
if (crIndex + 1 == newlineIndex)
|
|
|
|
return LineEndings::Windows;
|
|
|
|
|
|
|
|
qWarning().noquote() << "Invalid line ending in file, using default";
|
|
|
|
return defaultEndings;
|
|
|
|
}
|
|
|
|
return LineEndings::Unix;
|
|
|
|
}
|
|
|
|
if (crIndex >= 0) {
|
|
|
|
return LineEndings::OldMacOs;
|
|
|
|
}
|
|
|
|
|
|
|
|
qWarning().noquote() << "Unknown line ending in file, using default";
|
|
|
|
return defaultEndings;
|
|
|
|
}
|
|
|
|
|
|
|
|
QQmlFormatOptionLineEndings QQmlFormatOptions::parseEndings(const QString &endings)
|
|
|
|
{
|
|
|
|
if (endings == u"unix")
|
|
|
|
return Unix;
|
|
|
|
if (endings == u"windows")
|
|
|
|
return Windows;
|
|
|
|
if (endings == u"macos")
|
|
|
|
return OldMacOs;
|
|
|
|
if (endings == u"native")
|
|
|
|
return Native;
|
|
|
|
|
|
|
|
qWarning().noquote() << "Unknown line ending type" << endings << ", using default";
|
|
|
|
#if defined(Q_OS_WIN)
|
|
|
|
return Windows;
|
|
|
|
#else
|
|
|
|
return Unix;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void QQmlFormatOptions::applySettings(const QQmlFormatSettings &settings)
|
|
|
|
{
|
2025-01-24 12:51:04 +00:00
|
|
|
// If the options is already set by commandline, don't override it with the values in the .ini
|
|
|
|
// file.
|
|
|
|
if (!isMarked(Settings::IndentWidth)
|
|
|
|
&& settings.isSet(QQmlFormatSettings::s_indentWidthSetting)) {
|
|
|
|
setIndentWidth(settings.value(QQmlFormatSettings::s_indentWidthSetting).toInt());
|
2024-09-13 17:18:28 +00:00
|
|
|
}
|
|
|
|
|
2025-01-24 12:51:04 +00:00
|
|
|
if (!isMarked(Settings::UseTabs) && settings.isSet(QQmlFormatSettings::s_useTabsSetting)) {
|
|
|
|
setTabsEnabled(settings.value(QQmlFormatSettings::s_useTabsSetting).toBool());
|
2024-11-07 16:01:17 +00:00
|
|
|
}
|
|
|
|
|
2025-01-24 12:51:04 +00:00
|
|
|
if (!isMarked(Settings::MaxColumnWidth)
|
|
|
|
&& settings.isSet(QQmlFormatSettings::s_maxColumnWidthSetting)) {
|
|
|
|
setMaxColumnWidth(settings.value(QQmlFormatSettings::s_maxColumnWidthSetting).toInt());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isMarked(Settings::NormalizeOrder)
|
|
|
|
&& settings.isSet(QQmlFormatSettings::s_normalizeSetting)) {
|
2024-09-13 17:18:28 +00:00
|
|
|
setNormalizeEnabled(settings.value(QQmlFormatSettings::s_normalizeSetting).toBool());
|
2025-01-24 12:51:04 +00:00
|
|
|
}
|
2024-09-13 17:18:28 +00:00
|
|
|
|
2025-01-24 12:51:04 +00:00
|
|
|
if (!isMarked(Settings::NewlineType) && settings.isSet(QQmlFormatSettings::s_newlineSetting)) {
|
2024-09-13 17:18:28 +00:00
|
|
|
setNewline(QQmlFormatOptions::parseEndings(
|
|
|
|
settings.value(QQmlFormatSettings::s_newlineSetting).toString()));
|
2025-01-24 12:51:04 +00:00
|
|
|
}
|
2024-09-13 17:18:28 +00:00
|
|
|
|
2025-01-24 12:51:04 +00:00
|
|
|
if (!isMarked(Settings::ObjectsSpacing)
|
|
|
|
&& settings.isSet(QQmlFormatSettings::s_objectsSpacingSetting)) {
|
2024-09-13 17:18:28 +00:00
|
|
|
setObjectsSpacing(settings.value(QQmlFormatSettings::s_objectsSpacingSetting).toBool());
|
2025-01-24 12:51:04 +00:00
|
|
|
}
|
2024-09-13 17:18:28 +00:00
|
|
|
|
2025-01-24 12:51:04 +00:00
|
|
|
if (!isMarked(Settings::FunctionsSpacing)
|
|
|
|
&& settings.isSet(QQmlFormatSettings::s_functionsSpacingSetting)) {
|
2024-09-13 17:18:28 +00:00
|
|
|
setFunctionsSpacing(settings.value(QQmlFormatSettings::s_functionsSpacingSetting).toBool());
|
2025-01-24 12:51:04 +00:00
|
|
|
}
|
2024-12-18 14:15:50 +00:00
|
|
|
|
2025-01-24 12:51:04 +00:00
|
|
|
if (!isMarked(Settings::SortImports)
|
|
|
|
&& settings.isSet(QQmlFormatSettings::s_sortImportsSetting)) {
|
2024-12-18 14:15:50 +00:00
|
|
|
setSortImports(settings.value(QQmlFormatSettings::s_sortImportsSetting).toBool());
|
2025-01-24 12:51:04 +00:00
|
|
|
}
|
2024-09-13 17:18:28 +00:00
|
|
|
}
|
2025-01-27 11:06:07 +00:00
|
|
|
|
|
|
|
QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList &args)
|
|
|
|
{
|
|
|
|
QQmlFormatOptions options;
|
|
|
|
QCommandLineParser parser;
|
|
|
|
parser.setApplicationDescription(
|
|
|
|
"Formats QML files according to the QML Coding Conventions."_L1);
|
|
|
|
parser.addHelpOption();
|
|
|
|
parser.addVersionOption();
|
|
|
|
|
|
|
|
parser.addOption(
|
|
|
|
QCommandLineOption({ "V"_L1, "verbose"_L1 },
|
|
|
|
QStringLiteral("Verbose mode. Outputs more detailed information.")));
|
|
|
|
|
|
|
|
QCommandLineOption writeDefaultsOption(
|
|
|
|
QStringList() << "write-defaults"_L1,
|
|
|
|
QLatin1String("Writes defaults settings to .qmlformat.ini and exits (Warning: This "
|
|
|
|
"will overwrite any existing settings and comments!)"_L1));
|
|
|
|
parser.addOption(writeDefaultsOption);
|
|
|
|
|
|
|
|
QCommandLineOption ignoreSettings(QStringList() << "ignore-settings"_L1,
|
|
|
|
QLatin1String("Ignores all settings files and only takes "
|
|
|
|
"command line options into consideration"_L1));
|
|
|
|
parser.addOption(ignoreSettings);
|
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption(
|
|
|
|
{ "i"_L1, "inplace"_L1 },
|
|
|
|
QStringLiteral("Edit file in-place instead of outputting to stdout.")));
|
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption({ "f"_L1, "force"_L1 },
|
|
|
|
QStringLiteral("Continue even if an error has occurred.")));
|
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption({ "t"_L1, "tabs"_L1 },
|
|
|
|
QStringLiteral("Use tabs instead of spaces.")));
|
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption({ "w"_L1, "indent-width"_L1 },
|
|
|
|
QStringLiteral("How many spaces are used when indenting."),
|
|
|
|
"width"_L1, "4"_L1));
|
|
|
|
|
|
|
|
QCommandLineOption columnWidthOption(
|
|
|
|
{ "W"_L1, "column-width"_L1 },
|
2025-06-27 11:48:27 +00:00
|
|
|
QStringLiteral("Breaks the line into multiple lines if exceedes the specified width. "
|
2025-01-27 11:06:07 +00:00
|
|
|
"Use -1 to disable line wrapping. (default)"),
|
|
|
|
"width"_L1, "-1"_L1);
|
|
|
|
parser.addOption(columnWidthOption);
|
|
|
|
parser.addOption(QCommandLineOption({ "n"_L1, "normalize"_L1 },
|
|
|
|
QStringLiteral("Reorders the attributes of the objects "
|
|
|
|
"according to the QML Coding Guidelines.")));
|
|
|
|
|
2025-02-10 09:57:15 +00:00
|
|
|
QCommandLineOption filesOption(
|
|
|
|
{ "F"_L1, "files"_L1 }, "Format all files listed in file, in-place"_L1, "file"_L1);
|
|
|
|
parser.addOption(filesOption);
|
2025-01-27 11:06:07 +00:00
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption(
|
|
|
|
{ "l"_L1, "newline"_L1 },
|
|
|
|
QStringLiteral("Override the new line format to use (native macos unix windows)."),
|
|
|
|
"newline"_L1, "native"_L1));
|
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption(
|
|
|
|
QStringList() << "objects-spacing"_L1,
|
|
|
|
QStringLiteral("Ensure spaces between objects (only works with normalize option).")));
|
|
|
|
|
|
|
|
parser.addOption(QCommandLineOption(
|
|
|
|
QStringList() << "functions-spacing"_L1,
|
|
|
|
QStringLiteral("Ensure spaces between functions (only works with normalize option).")));
|
|
|
|
|
|
|
|
parser.addOption(
|
|
|
|
QCommandLineOption({ "S"_L1, "sort-imports"_L1 },
|
|
|
|
QStringLiteral("Sort imports alphabetically "
|
|
|
|
"(Warning: this might change semantics if a given "
|
|
|
|
"name identifies types in multiple modules!).")));
|
qmlformat: customizable semicolon
Add semicolon option to qmlformat. While --Always always appends
semicolons to the JS statements, --essential removes the semicolons
unless it is not safe to rely on ASI once semicolons are removed.
Change the way EmptyStatements are handled. Prior to this
commit, semicolons following if, for, foreach, while statements
without bodies were added to the new line with some indentation.
Make the semicolon following no-body items stick to the right
paranthesis. If there is a chain of empty statements, write out a
single one.
[ChangeLog][qmlformat] New option semicolon-rule is added and
EmptyStatement formatting behavior has changed.
* Added --semicolon-rule option with modes:
- --semicolon-rule=always: Always appends semicolons to JS statements.
- --semicolon-rule=essential: Removes semicolons unless ASI makes it unsafe.
* Changed handling of EmptyStatements:
- Semicolons after control structures without a body (e.g., if, for, while)
now appear directly after the closing parenthesis, instead of on a new line.
- Consecutive empty statements are collapsed into a single semicolon.
Fixes: QTBUG-107152
Change-Id: Ic95047a1f0077937d4c1f01328d77a3e6a4f22d6
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
2025-03-24 11:52:33 +00:00
|
|
|
QCommandLineOption semicolonRuleOption(
|
|
|
|
QStringList() << "semicolon-rule"_L1,
|
|
|
|
QStringLiteral("Specify the semicolon rule to use (always, essential).\n"
|
|
|
|
"always: always adds semicolon [default].\n"
|
|
|
|
"essential: adds only when ASI wouldn't be relied on."),
|
|
|
|
"rule"_L1, "always"_L1);
|
|
|
|
parser.addOption(semicolonRuleOption);
|
2025-01-27 11:06:07 +00:00
|
|
|
|
|
|
|
parser.addPositionalArgument("filenames"_L1, "files to be processed by qmlformat"_L1);
|
|
|
|
|
|
|
|
parser.process(args);
|
|
|
|
|
|
|
|
if (parser.isSet(writeDefaultsOption)) {
|
|
|
|
options.setWriteDefaultSettingsEnabled(true);
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2025-02-10 09:57:15 +00:00
|
|
|
if (parser.positionalArguments().empty() && !parser.isSet(filesOption)) {
|
|
|
|
options.addError("Error: Expected at least one input file."_L1);
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2025-01-27 11:06:07 +00:00
|
|
|
bool indentWidthOkay = false;
|
|
|
|
const int indentWidth = parser.value("indent-width"_L1).toInt(&indentWidthOkay);
|
|
|
|
if (!indentWidthOkay) {
|
|
|
|
options.addError("Error: Invalid value passed to -w"_L1);
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList files;
|
|
|
|
if (!parser.value("files"_L1).isEmpty()) {
|
2025-02-10 09:57:15 +00:00
|
|
|
const QString path = parser.value("files"_L1);
|
|
|
|
QFile file(path);
|
|
|
|
if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
|
2025-02-28 15:09:06 +00:00
|
|
|
options.addError("Error: Could not open file \""_L1 + path + "\" for option -F."_L1);
|
2025-02-10 09:57:15 +00:00
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTextStream in(&file);
|
|
|
|
while (!in.atEnd()) {
|
|
|
|
QString file = in.readLine();
|
2025-01-27 11:06:07 +00:00
|
|
|
|
2025-02-10 09:57:15 +00:00
|
|
|
if (file.isEmpty())
|
|
|
|
continue;
|
2025-01-27 11:06:07 +00:00
|
|
|
|
2025-02-10 09:57:15 +00:00
|
|
|
files.push_back(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (files.isEmpty()) {
|
|
|
|
options.addError("Error: File \""_L1 + path + "\" for option -F is empty."_L1);
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &file : std::as_const(files)) {
|
|
|
|
if (!QFile::exists(file)) {
|
2025-02-25 21:43:41 +00:00
|
|
|
options.addError("Error: Entry \""_L1 + file + "\" of file \""_L1 + path
|
|
|
|
+ "\" passed to option -F could not be found."_L1);
|
2025-02-10 09:57:15 +00:00
|
|
|
return options;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const auto &args = parser.positionalArguments();
|
|
|
|
for (const auto &file : args) {
|
|
|
|
if (!QFile::exists(file)) {
|
2025-02-25 21:43:41 +00:00
|
|
|
options.addError("Error: Could not find file \""_L1 + file + "\"."_L1);
|
2025-02-10 09:57:15 +00:00
|
|
|
return options;
|
2025-01-27 11:06:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
options.setIsVerbose(parser.isSet("verbose"_L1));
|
|
|
|
options.setIsInplace(parser.isSet("inplace"_L1));
|
|
|
|
options.setForceEnabled(parser.isSet("force"_L1));
|
|
|
|
options.setIgnoreSettingsEnabled(parser.isSet("ignore-settings"_L1));
|
2025-01-24 12:51:04 +00:00
|
|
|
|
|
|
|
if (parser.isSet("tabs"_L1)) {
|
|
|
|
options.mark(Settings::UseTabs);
|
|
|
|
options.setTabsEnabled(true);
|
|
|
|
}
|
|
|
|
if (parser.isSet("normalize"_L1)) {
|
|
|
|
options.mark(Settings::NormalizeOrder);
|
|
|
|
options.setNormalizeEnabled(true);
|
|
|
|
}
|
|
|
|
if (parser.isSet("objects-spacing"_L1)) {
|
|
|
|
options.mark(Settings::ObjectsSpacing);
|
|
|
|
options.setObjectsSpacing(true);
|
|
|
|
}
|
|
|
|
if (parser.isSet("functions-spacing"_L1)) {
|
|
|
|
options.mark(Settings::FunctionsSpacing);
|
|
|
|
options.setFunctionsSpacing(true);
|
|
|
|
}
|
|
|
|
if (parser.isSet("sort-imports"_L1)) {
|
|
|
|
options.mark(Settings::SortImports);
|
|
|
|
options.setSortImports(true);
|
|
|
|
}
|
|
|
|
if (parser.isSet("indent-width"_L1)) {
|
|
|
|
options.mark(Settings::IndentWidth);
|
|
|
|
options.setIndentWidth(indentWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parser.isSet("newline"_L1)) {
|
|
|
|
options.mark(Settings::NewlineType);
|
|
|
|
options.setNewline(QQmlFormatOptions::parseEndings(parser.value("newline"_L1)));
|
|
|
|
}
|
qmlformat: customizable semicolon
Add semicolon option to qmlformat. While --Always always appends
semicolons to the JS statements, --essential removes the semicolons
unless it is not safe to rely on ASI once semicolons are removed.
Change the way EmptyStatements are handled. Prior to this
commit, semicolons following if, for, foreach, while statements
without bodies were added to the new line with some indentation.
Make the semicolon following no-body items stick to the right
paranthesis. If there is a chain of empty statements, write out a
single one.
[ChangeLog][qmlformat] New option semicolon-rule is added and
EmptyStatement formatting behavior has changed.
* Added --semicolon-rule option with modes:
- --semicolon-rule=always: Always appends semicolons to JS statements.
- --semicolon-rule=essential: Removes semicolons unless ASI makes it unsafe.
* Changed handling of EmptyStatements:
- Semicolons after control structures without a body (e.g., if, for, while)
now appear directly after the closing parenthesis, instead of on a new line.
- Consecutive empty statements are collapsed into a single semicolon.
Fixes: QTBUG-107152
Change-Id: Ic95047a1f0077937d4c1f01328d77a3e6a4f22d6
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
2025-03-24 11:52:33 +00:00
|
|
|
|
|
|
|
if (parser.isSet(semicolonRuleOption)) {
|
|
|
|
options.mark(Settings::SemicolonRule);
|
|
|
|
const auto value = parser.value(semicolonRuleOption);
|
|
|
|
if (value == "always"_L1) {
|
|
|
|
options.setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Always);
|
|
|
|
} else if (value == "essential"_L1) {
|
|
|
|
options.setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Essential);
|
|
|
|
} else {
|
|
|
|
options.addError("Error: Invalid value passed to --semicolon-rule."_L1);
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
}
|
2025-01-27 11:06:07 +00:00
|
|
|
options.setFiles(files);
|
|
|
|
options.setArguments(parser.positionalArguments());
|
|
|
|
|
|
|
|
if (parser.isSet(columnWidthOption)) {
|
|
|
|
bool isValidValue = false;
|
|
|
|
const int maxColumnWidth = parser.value(columnWidthOption).toInt(&isValidValue);
|
|
|
|
if (!isValidValue || maxColumnWidth < -1) {
|
|
|
|
options.addError("Error: Invalid value passed to -W. Must be an integer >= -1"_L1);
|
|
|
|
return options;
|
|
|
|
}
|
2025-01-24 12:51:04 +00:00
|
|
|
options.mark(Settings::MaxColumnWidth);
|
2025-01-27 11:06:07 +00:00
|
|
|
options.setMaxColumnWidth(maxColumnWidth);
|
|
|
|
}
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
QQmlFormatOptions QQmlFormatOptions::optionsForFile(const QString &fileName,
|
|
|
|
QQmlFormatSettings *settings) const
|
|
|
|
{
|
|
|
|
// Perform formatting inplace if --files option is set.
|
|
|
|
const bool hasFiles = !files().isEmpty();
|
|
|
|
QQmlFormatOptions perFileOptions = *this;
|
|
|
|
if (hasFiles)
|
|
|
|
perFileOptions.setIsInplace(true);
|
|
|
|
|
2025-07-07 09:27:01 +00:00
|
|
|
if (!ignoreSettingsEnabled() && settings->search(fileName).isValid())
|
2025-01-27 11:06:07 +00:00
|
|
|
perFileOptions.applySettings(*settings);
|
|
|
|
|
|
|
|
return perFileOptions;
|
|
|
|
}
|