qtdeclarative/src/qmlformat/qqmlformatoptions.cpp

338 lines
13 KiB
C++

// 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"
#include "qqmlformatsettings_p.h"
#include <QCommandLineParser>
#include <QCommandLineOption>
using namespace Qt::StringLiterals;
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)
{
// 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());
}
if (!isMarked(Settings::UseTabs) && settings.isSet(QQmlFormatSettings::s_useTabsSetting)) {
setTabsEnabled(settings.value(QQmlFormatSettings::s_useTabsSetting).toBool());
}
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)) {
setNormalizeEnabled(settings.value(QQmlFormatSettings::s_normalizeSetting).toBool());
}
if (!isMarked(Settings::NewlineType) && settings.isSet(QQmlFormatSettings::s_newlineSetting)) {
setNewline(QQmlFormatOptions::parseEndings(
settings.value(QQmlFormatSettings::s_newlineSetting).toString()));
}
if (!isMarked(Settings::ObjectsSpacing)
&& settings.isSet(QQmlFormatSettings::s_objectsSpacingSetting)) {
setObjectsSpacing(settings.value(QQmlFormatSettings::s_objectsSpacingSetting).toBool());
}
if (!isMarked(Settings::FunctionsSpacing)
&& settings.isSet(QQmlFormatSettings::s_functionsSpacingSetting)) {
setFunctionsSpacing(settings.value(QQmlFormatSettings::s_functionsSpacingSetting).toBool());
}
if (!isMarked(Settings::SortImports)
&& settings.isSet(QQmlFormatSettings::s_sortImportsSetting)) {
setSortImports(settings.value(QQmlFormatSettings::s_sortImportsSetting).toBool());
}
}
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 },
QStringLiteral("Breaks the line into multiple lines if exceedes the specified width. "
"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.")));
QCommandLineOption filesOption(
{ "F"_L1, "files"_L1 }, "Format all files listed in file, in-place"_L1, "file"_L1);
parser.addOption(filesOption);
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!).")));
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);
QCommandLineOption dryrunOption(
QStringList() << "dry-run"_L1,
QStringLiteral("Prints the settings file that would be used for this instance."
"This is useful to see what settings would be used "
"without actually performing anything."));
parser.addOption(dryrunOption);
parser.addPositionalArgument("filenames"_L1, "files to be processed by qmlformat"_L1);
parser.process(args);
if (parser.isSet(writeDefaultsOption)) {
options.setWriteDefaultSettingsEnabled(true);
return options;
}
if (parser.positionalArguments().empty() && !parser.isSet(filesOption)) {
options.addError("Error: Expected at least one input file."_L1);
return options;
}
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()) {
const QString path = parser.value("files"_L1);
QFile file(path);
if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
options.addError("Error: Could not open file \""_L1 + path + "\" for option -F."_L1);
return options;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString file = in.readLine();
if (file.isEmpty())
continue;
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)) {
options.addError("Error: Entry \""_L1 + file + "\" of file \""_L1 + path
+ "\" passed to option -F could not be found."_L1);
return options;
}
}
} else {
const auto &args = parser.positionalArguments();
for (const auto &file : args) {
if (!QFile::exists(file)) {
options.addError("Error: Could not find file \""_L1 + file + "\"."_L1);
return options;
}
}
}
options.setDryRun(parser.isSet(dryrunOption));
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));
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)));
}
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;
}
}
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;
}
options.mark(Settings::MaxColumnWidth);
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);
if (!ignoreSettingsEnabled() && settings->search(fileName, { m_verbose }).isValid())
perFileOptions.applySettings(*settings);
return perFileOptions;
}