Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlformatoptions.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
7
8#include <QCommandLineParser>
9#include <QCommandLineOption>
10
11using namespace Qt::StringLiterals;
12
22
23QQmlFormatOptions::LineEndings QQmlFormatOptions::detectLineEndings(const QString &code)
24{
25 const QQmlJS::Dom::LineWriterOptions::LineEndings defaultEndings =
26#if defined(Q_OS_WIN)
27 LineEndings::Windows;
28#else
29 LineEndings::Unix;
30#endif
31 // find out current line endings...
32 int newlineIndex = code.indexOf(QChar(u'\n'));
33 int crIndex = code.indexOf(QChar(u'\r'));
34 if (newlineIndex >= 0) {
35 if (crIndex >= 0) {
36 if (crIndex + 1 == newlineIndex)
37 return LineEndings::Windows;
38
39 qWarning().noquote() << "Invalid line ending in file, using default";
40 return defaultEndings;
41 }
42 return LineEndings::Unix;
43 }
44 if (crIndex >= 0) {
45 return LineEndings::OldMacOs;
46 }
47
48 qWarning().noquote() << "Unknown line ending in file, using default";
49 return defaultEndings;
50}
51
52QQmlFormatOptionLineEndings QQmlFormatOptions::parseEndings(const QString &endings)
53{
54 if (endings == u"unix")
55 return Unix;
56 if (endings == u"windows")
57 return Windows;
58 if (endings == u"macos")
59 return OldMacOs;
60 if (endings == u"native")
61 return Native;
62
63 qWarning().noquote() << "Unknown line ending type" << endings << ", using default";
64#if defined(Q_OS_WIN)
65 return Windows;
66#else
67 return Unix;
68#endif
69}
70
72 if (value == "always"_L1) {
73 return QQmlJS::Dom::LineWriterOptions::SemicolonRule::Always;
74 } else if (value == "essential"_L1) {
75 return QQmlJS::Dom::LineWriterOptions::SemicolonRule::Essential;
76 } else {
77 return std::nullopt;
78 }
79}
80
81void QQmlFormatOptions::applySettings(const QQmlFormatSettings &settings)
82{
83 // If the options is already set by commandline, don't override it with the values in the .ini
84 // file.
85 if (!isMarked(Settings::IndentWidth)
86 && settings.isSet(QQmlFormatSettings::s_indentWidthSetting)) {
87 setIndentWidth(settings.value(QQmlFormatSettings::s_indentWidthSetting).toInt());
88 }
89
90 if (!isMarked(Settings::UseTabs) && settings.isSet(QQmlFormatSettings::s_useTabsSetting)) {
91 setTabsEnabled(settings.value(QQmlFormatSettings::s_useTabsSetting).toBool());
92 }
93
94 if (!isMarked(Settings::MaxColumnWidth)
95 && settings.isSet(QQmlFormatSettings::s_maxColumnWidthSetting)) {
96 setMaxColumnWidth(settings.value(QQmlFormatSettings::s_maxColumnWidthSetting).toInt());
97 }
98
99 if (!isMarked(Settings::NormalizeOrder)
100 && settings.isSet(QQmlFormatSettings::s_normalizeSetting)) {
101 setNormalizeEnabled(settings.value(QQmlFormatSettings::s_normalizeSetting).toBool());
102 }
103
104 if (!isMarked(Settings::NewlineType) && settings.isSet(QQmlFormatSettings::s_newlineSetting)) {
105 setNewline(QQmlFormatOptions::parseEndings(
106 settings.value(QQmlFormatSettings::s_newlineSetting).toString()));
107 }
108
109 if (!isMarked(Settings::ObjectsSpacing)
110 && settings.isSet(QQmlFormatSettings::s_objectsSpacingSetting)) {
111 setObjectsSpacing(settings.value(QQmlFormatSettings::s_objectsSpacingSetting).toBool());
112 }
113
114 if (!isMarked(Settings::FunctionsSpacing)
115 && settings.isSet(QQmlFormatSettings::s_functionsSpacingSetting)) {
116 setFunctionsSpacing(settings.value(QQmlFormatSettings::s_functionsSpacingSetting).toBool());
117 }
118
119 // Needs to be set after normalize since it can set NormalizeOrder to true.
121 && settings.isSet(QQmlFormatSettings::s_groupAttributesTogetherSetting)) {
123 settings.value(QQmlFormatSettings::s_groupAttributesTogetherSetting).toBool());
124 }
125
126 if (!isMarked(Settings::SortImports)
127 && settings.isSet(QQmlFormatSettings::s_sortImportsSetting)) {
128 setSortImports(settings.value(QQmlFormatSettings::s_sortImportsSetting).toBool());
129 }
130
132 && settings.isSet(QQmlFormatSettings::s_singleLineEmptyObjectsSetting)) {
133 setSingleLineEmptyObjects(settings.value(QQmlFormatSettings::s_singleLineEmptyObjectsSetting).toBool());
134 }
135
136 if (!isMarked(Settings::SemicolonRule)
137 && settings.isSet(QQmlFormatSettings::s_semiColonRuleSetting)) {
138 const auto semicolonRule = parseSemicolonRule(
139 settings.value(QQmlFormatSettings::s_semiColonRuleSetting).toString());
140 if (!semicolonRule.has_value()) {
141 qWarning().noquote() << "Invalid semicolon rule in settings file, using 'always'";
142 setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Always);
143 } else {
144 setSemicolonRule(semicolonRule.value());
145 }
146 }
147}
148
149QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList &args)
150{
151 QQmlFormatOptions options;
152 QCommandLineParser parser;
153 parser.setApplicationDescription(
154 "Formats QML files according to the QML Coding Conventions.\n"_L1
155 "Options below the \"Formatting options\" section can also be set via .qmlformat.ini"_L1
156 " unless --ignore-settings is used"_L1);
157 parser.addHelpOption();
158 parser.addVersionOption();
159
160 //
161 // options that only are set via CLI
162 //
163 parser.addOption(
164 QCommandLineOption({ "V"_L1, "verbose"_L1 },
165 QStringLiteral("Verbose mode. Outputs more detailed information.")));
166
167 QCommandLineOption writeDefaultsOption(
168 QStringList() << "write-defaults"_L1,
169 QLatin1String("Writes defaults settings to .qmlformat.ini and exits (Warning: This "
170 "will overwrite any existing settings and comments!)"_L1));
171 parser.addOption(writeDefaultsOption);
172
173 QCommandLineOption outputOptionsOption(
174 QStringList() << "output-options"_L1,
175 QLatin1String("Output available options and their defaults values in JSON format."_L1));
176 parser.addOption(outputOptionsOption);
177
178 QCommandLineOption ignoreSettings(QStringList() << "ignore-settings"_L1,
179 QLatin1String("Ignores all settings files and only takes "
180 "command line options into consideration"_L1));
181 parser.addOption(ignoreSettings);
182
183 QCommandLineOption filesOption(
184 { "F"_L1, "files"_L1 }, "Format all files listed in file, in-place"_L1, "file"_L1);
185 parser.addOption(filesOption);
186
187
188 QCommandLineOption dryrunOption(
189 QStringList() << "dry-run"_L1,
190 QStringLiteral("Prints the settings file that would be used for this instance."
191 "This is useful to see what settings would be used "
192 "without actually performing anything."));
193 parser.addOption(dryrunOption);
194
195 QCommandLineOption settingsOption(
196 { "s"_L1, "settings"_L1 },
197 QStringLiteral("Use the specified .qmlformat.ini file as the only configuration source."
198 "Overrides any per-directory configuration lookup."),
199 "file"_L1);
200 parser.addOption(settingsOption);
201
202 parser.addOption(QCommandLineOption(
203 { "i"_L1, "inplace"_L1 },
204 QStringLiteral("Edit file in-place instead of outputting to stdout.")));
205
206 // Note the blatant abuse of the option's help text to add a "section marker"
207 // Therefore, this needs to come last. Also, on Windows, the unicode characters seem to cause issues
208 parser.addOption(QCommandLineOption({ "f"_L1, "force"_L1 },
209 #ifdef Q_OS_WINDOWS
210 "Continue even if an error has occurred.\n<><><><><><><><><>\nFormatting options\n<><><><><><><><><>"_L1
211 #else
212 u"Continue even if an error has occurred.\n♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦\nFormatting options\n♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦"_s
213 #endif
214 ));
215
216 //
217 // options that can be configured by qmlformat.ini
218 //
219
220 parser.addOption(QCommandLineOption({ "t"_L1, "tabs"_L1 },
221 QStringLiteral("Use tabs instead of spaces.")));
222
223 parser.addOption(QCommandLineOption({ "w"_L1, "indent-width"_L1 },
224 QStringLiteral("How many spaces are used when indenting."),
225 "width"_L1, "4"_L1));
226
227 QCommandLineOption columnWidthOption(
228 { "W"_L1, "column-width"_L1 },
229 QStringLiteral("Breaks the line into multiple lines if exceedes the specified width. "
230 "Use -1 to disable line wrapping. (default)"),
231 "width"_L1, "-1"_L1);
232 parser.addOption(columnWidthOption);
233 parser.addOption(QCommandLineOption({ "n"_L1, "normalize"_L1 },
234 QStringLiteral("Reorders the attributes of the objects "
235 "according to the QML Coding Guidelines.")));
236
237
238 parser.addOption(QCommandLineOption(
239 { "l"_L1, "newline"_L1 },
240 QStringLiteral("Override the new line format to use (native macos unix windows)."),
241 "newline"_L1, "native"_L1));
242
243 parser.addOption(QCommandLineOption(
244 QStringList() << "objects-spacing"_L1,
245 QStringLiteral("Ensure spaces between objects (only works with normalize option).")));
246
247 parser.addOption(QCommandLineOption(
248 QStringList() << "functions-spacing"_L1,
249 QStringLiteral("Ensure spaces between functions (only works with normalize option).")));
250
251 parser.addOption(
252 QCommandLineOption(QStringList() << "group-attributes-together"_L1,
253 QStringLiteral("Reorders and groups the attributes of the objects "
254 "according to the QML Coding Guidelines. "
255 "Implies --normalize.")));
256
257 parser.addOption(
258 QCommandLineOption({ "S"_L1, "sort-imports"_L1 },
259 QStringLiteral("Sort imports alphabetically "
260 "(Warning: this might change semantics if a given "
261 "name identifies types in multiple modules!).")));
262
263 parser.addOption(QCommandLineOption(
264 QStringList() << "single-line-empty-objects"_L1,
265 QStringLiteral("Write empty objects on a single line (only works with normalize option).")));
266
267 QCommandLineOption semicolonRuleOption(
268 QStringList() << "semicolon-rule"_L1,
269 QStringLiteral("Specify the semicolon rule to use (always, essential).\n"
270 "always: always adds semicolon [default].\n"
271 "essential: adds only when ASI wouldn't be relied on."),
272 "rule"_L1, "always"_L1);
273 parser.addOption(semicolonRuleOption);
274
275 parser.addPositionalArgument("filenames"_L1, "files to be processed by qmlformat"_L1);
276
277 parser.process(args);
278
279 if (parser.isSet(writeDefaultsOption)) {
281 return options;
282 }
283
284 if (parser.isSet(outputOptionsOption)) {
286 return options;
287 }
288
289 if (parser.positionalArguments().empty() && !parser.isSet(filesOption)) {
290 options.addError("Error: Expected at least one input file."_L1);
291 return options;
292 }
293
294 bool indentWidthOkay = false;
295 const int indentWidth = parser.value("indent-width"_L1).toInt(&indentWidthOkay);
296 if (!indentWidthOkay) {
297 options.addError("Error: Invalid value passed to -w"_L1);
298 return options;
299 }
300
301 QStringList files;
302 if (!parser.value("files"_L1).isEmpty()) {
303 const QString path = parser.value("files"_L1);
304 QFile file(path);
305 if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
306 options.addError("Error: Could not open file \""_L1 + path + "\" for option -F."_L1);
307 return options;
308 }
309
310 QTextStream in(&file);
311 while (!in.atEnd()) {
312 QString file = in.readLine();
313
314 if (file.isEmpty())
315 continue;
316
317 files.push_back(file);
318 }
319
320 if (files.isEmpty()) {
321 options.addError("Error: File \""_L1 + path + "\" for option -F is empty."_L1);
322 return options;
323 }
324
325 for (const auto &file : std::as_const(files)) {
326 if (!QFile::exists(file)) {
327 options.addError("Error: Entry \""_L1 + file + "\" of file \""_L1 + path
328 + "\" passed to option -F could not be found."_L1);
329 return options;
330 }
331 }
332 } else {
333 const auto &args = parser.positionalArguments();
334 for (const auto &file : args) {
335 if (!QFile::exists(file)) {
336 options.addError("Error: Could not find file \""_L1 + file + "\"."_L1);
337 return options;
338 }
339 }
340 }
341
342 options.setDryRun(parser.isSet(dryrunOption));
343 options.setIsVerbose(parser.isSet("verbose"_L1));
344 options.setIsInplace(parser.isSet("inplace"_L1));
345 options.setForceEnabled(parser.isSet("force"_L1));
346 options.setIgnoreSettingsEnabled(parser.isSet("ignore-settings"_L1));
347
348 if (parser.isSet("tabs"_L1)) {
349 options.mark(Settings::UseTabs);
350 options.setTabsEnabled(true);
351 }
352 if (parser.isSet("normalize"_L1)) {
353 options.mark(Settings::NormalizeOrder);
354 options.setNormalizeEnabled(true);
355 }
356 if (parser.isSet("objects-spacing"_L1)) {
357 options.mark(Settings::ObjectsSpacing);
358 options.setObjectsSpacing(true);
359 }
360 if (parser.isSet("functions-spacing"_L1)) {
361 options.mark(Settings::FunctionsSpacing);
362 options.setFunctionsSpacing(true);
363 }
364 // Needs to be set after normalize since it can set NormalizeOrder to true.
365 if (parser.isSet("group-attributes-together"_L1)) {
368 }
369 if (parser.isSet("sort-imports"_L1)) {
370 options.mark(Settings::SortImports);
371 options.setSortImports(true);
372 }
373 if (parser.isSet("single-line-empty-objects"_L1)) {
376 }
377 if (parser.isSet("indent-width"_L1)) {
378 options.mark(Settings::IndentWidth);
379 options.setIndentWidth(indentWidth);
380 }
381
382 if (parser.isSet("newline"_L1)) {
383 options.mark(Settings::NewlineType);
384 options.setNewline(QQmlFormatOptions::parseEndings(parser.value("newline"_L1)));
385 }
386
387 if (parser.isSet(settingsOption)) {
388 options.mark(Settings::SettingsFile);
389 const auto value = parser.value(settingsOption);
390 if (value.isEmpty()) {
391 options.addError("Error: No settings file specified for option -s."_L1);
392 return options;
393 }
394 if (!QFile::exists(value)) {
395 options.addError("Error: Could not find file \""_L1 + value + "\"."_L1);
396 return options;
397 }
398 options.setSettingsFile(value);
399 }
400
401 if (parser.isSet(semicolonRuleOption)) {
402 options.mark(Settings::SemicolonRule);
403 const auto value = parser.value(semicolonRuleOption);
404 auto semicolonRule = parseSemicolonRule(value);
405 if (!semicolonRule.has_value()) {
406 options.addError("Error: Invalid value passed to --semicolon-rule. Must be 'always' or 'essential'."_L1);
407 return options;
408 }
409 options.setSemicolonRule(semicolonRule.value());
410 }
411 options.setFiles(files);
412 options.setArguments(parser.positionalArguments());
413
414 if (parser.isSet(columnWidthOption)) {
415 bool isValidValue = false;
416 const int maxColumnWidth = parser.value(columnWidthOption).toInt(&isValidValue);
417 if (!isValidValue || maxColumnWidth < -1) {
418 options.addError("Error: Invalid value passed to -W. Must be an integer >= -1"_L1);
419 return options;
420 }
421 options.mark(Settings::MaxColumnWidth);
422 options.setMaxColumnWidth(maxColumnWidth);
423 }
424 return options;
425}
426
428 QQmlFormatSettings *settings) const
429{
430 // Perform formatting inplace if --files option is set.
431 const bool hasFiles = !files().isEmpty();
432 QQmlFormatOptions perFileOptions = *this;
433 if (hasFiles)
434 perFileOptions.setIsInplace(true);
435
437 && settings->search(fileName, { m_settingsFile, m_verbose }).isValid())
438 perFileOptions.applySettings(*settings);
439
440 return perFileOptions;
441}
void setTabsEnabled(bool tabs)
void setSingleLineEmptyObjects(bool singleLineEmptyObjects)
void setNewline(const QQmlFormatOptionLineEndings &endings)
void setNormalizeEnabled(bool normalize)
void setOutputOptionsEnabled(bool newOutputOptions)
void setMaxColumnWidth(int width)
void applySettings(const QQmlFormatSettings &settings)
void setForceEnabled(bool newForce)
void setIgnoreSettingsEnabled(bool newIgnoreSettings)
void setIsVerbose(bool newVerbose)
void setSortImports(bool sort)
void setFunctionsSpacing(bool spacing)
void setIndentWidth(int width)
void setObjectsSpacing(bool spacing)
bool ignoreSettingsEnabled() const
void setIsInplace(bool newInplace)
void setWriteDefaultSettingsEnabled(bool newWriteDefaultSettings)
void setDryRun(bool newDryRun)
void setGroupAttributesTogether(bool keep)
QQmlFormatOptions optionsForFile(const QString &fileName, QQmlFormatSettings *settings) const
std::optional< QQmlJS::Dom::LineWriterOptions::SemicolonRule > parseSemicolonRule(const QString &value)