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 and sorts the attributes of the objects "
235 "according to the QML Coding Guidelines. "
236 "Incompatible with --group-attributes-together.")));
237
238
239 parser.addOption(QCommandLineOption(
240 { "l"_L1, "newline"_L1 },
241 QStringLiteral("Override the new line format to use (native macos unix windows)."),
242 "newline"_L1, "native"_L1));
243
244 parser.addOption(QCommandLineOption(
245 QStringList() << "objects-spacing"_L1,
246 QStringLiteral("Ensure spaces between objects (only works with normalize or group-attributes-together option).")));
247
248 parser.addOption(QCommandLineOption(
249 QStringList() << "functions-spacing"_L1,
250 QStringLiteral("Ensure spaces between functions (only works with normalize or group-attributes-together option).")));
251
252 parser.addOption(
253 QCommandLineOption(QStringList() << "group-attributes-together"_L1,
254 QStringLiteral("Reorders but does not sort the attributes of the objects "
255 "according to the QML Coding Guidelines. "
256 "Incompatible with --normalize.")));
257
258 parser.addOption(
259 QCommandLineOption({ "S"_L1, "sort-imports"_L1 },
260 QStringLiteral("Sort imports alphabetically "
261 "(Warning: this might change semantics if a given "
262 "name identifies types in multiple modules!).")));
263
264 parser.addOption(QCommandLineOption(
265 QStringList() << "single-line-empty-objects"_L1,
266 QStringLiteral("Write empty objects on a single line (only works with normalize or group-attributes-together option).")));
267
268 QCommandLineOption semicolonRuleOption(
269 QStringList() << "semicolon-rule"_L1,
270 QStringLiteral("Specify the semicolon rule to use (always, essential).\n"
271 "always: always adds semicolon [default].\n"
272 "essential: adds only when ASI wouldn't be relied on."),
273 "rule"_L1, "always"_L1);
274 parser.addOption(semicolonRuleOption);
275
276 parser.addPositionalArgument("filenames"_L1, "files to be processed by qmlformat"_L1);
277
278 parser.process(args);
279
280 if (parser.isSet(writeDefaultsOption)) {
282 return options;
283 }
284
285 if (parser.isSet(outputOptionsOption)) {
287 return options;
288 }
289
290 if (parser.positionalArguments().empty() && !parser.isSet(filesOption)) {
291 options.addError("Error: Expected at least one input file."_L1);
292 return options;
293 }
294
295 bool indentWidthOkay = false;
296 const int indentWidth = parser.value("indent-width"_L1).toInt(&indentWidthOkay);
297 if (!indentWidthOkay) {
298 options.addError("Error: Invalid value passed to -w"_L1);
299 return options;
300 }
301
302 QStringList files;
303 if (!parser.value("files"_L1).isEmpty()) {
304 const QString path = parser.value("files"_L1);
305 QFile file(path);
306 if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
307 options.addError("Error: Could not open file \""_L1 + path + "\" for option -F."_L1);
308 return options;
309 }
310
311 QTextStream in(&file);
312 while (!in.atEnd()) {
313 QString file = in.readLine();
314
315 if (file.isEmpty())
316 continue;
317
318 files.push_back(file);
319 }
320
321 if (files.isEmpty()) {
322 options.addError("Error: File \""_L1 + path + "\" for option -F is empty."_L1);
323 return options;
324 }
325
326 for (const auto &file : std::as_const(files)) {
327 if (!QFile::exists(file)) {
328 options.addError("Error: Entry \""_L1 + file + "\" of file \""_L1 + path
329 + "\" passed to option -F could not be found."_L1);
330 return options;
331 }
332 }
333 } else {
334 const auto &args = parser.positionalArguments();
335 for (const auto &file : args) {
336 if (!QFile::exists(file)) {
337 options.addError("Error: Could not find file \""_L1 + file + "\"."_L1);
338 return options;
339 }
340 }
341 }
342
343 options.setDryRun(parser.isSet(dryrunOption));
344 options.setIsVerbose(parser.isSet("verbose"_L1));
345 options.setIsInplace(parser.isSet("inplace"_L1));
346 options.setForceEnabled(parser.isSet("force"_L1));
347 options.setIgnoreSettingsEnabled(parser.isSet("ignore-settings"_L1));
348
349 if (parser.isSet("tabs"_L1)) {
350 options.mark(Settings::UseTabs);
351 options.setTabsEnabled(true);
352 }
353 if (parser.isSet("normalize"_L1)) {
354 options.mark(Settings::NormalizeOrder);
355 options.setNormalizeEnabled(true);
356 }
357 if (parser.isSet("objects-spacing"_L1)) {
358 options.mark(Settings::ObjectsSpacing);
359 options.setObjectsSpacing(true);
360 }
361 if (parser.isSet("functions-spacing"_L1)) {
362 options.mark(Settings::FunctionsSpacing);
363 options.setFunctionsSpacing(true);
364 }
365 // Needs to be set after normalize since it can set NormalizeOrder to true.
366 if (parser.isSet("group-attributes-together"_L1)) {
369 }
370 if (parser.isSet("sort-imports"_L1)) {
371 options.mark(Settings::SortImports);
372 options.setSortImports(true);
373 }
374 if (parser.isSet("single-line-empty-objects"_L1)) {
377 }
378 if (parser.isSet("indent-width"_L1)) {
379 options.mark(Settings::IndentWidth);
380 options.setIndentWidth(indentWidth);
381 }
382
383 if (parser.isSet("newline"_L1)) {
384 options.mark(Settings::NewlineType);
385 options.setNewline(QQmlFormatOptions::parseEndings(parser.value("newline"_L1)));
386 }
387
388 if (parser.isSet(settingsOption)) {
389 options.mark(Settings::SettingsFile);
390 const auto value = parser.value(settingsOption);
391 if (value.isEmpty()) {
392 options.addError("Error: No settings file specified for option -s."_L1);
393 return options;
394 }
395 if (!QFile::exists(value)) {
396 options.addError("Error: Could not find file \""_L1 + value + "\"."_L1);
397 return options;
398 }
399 options.setSettingsFile(value);
400 }
401
402 if (parser.isSet(semicolonRuleOption)) {
403 options.mark(Settings::SemicolonRule);
404 const auto value = parser.value(semicolonRuleOption);
405 auto semicolonRule = parseSemicolonRule(value);
406 if (!semicolonRule.has_value()) {
407 options.addError("Error: Invalid value passed to --semicolon-rule. Must be 'always' or 'essential'."_L1);
408 return options;
409 }
410 options.setSemicolonRule(semicolonRule.value());
411 }
412 options.setFiles(files);
413 options.setArguments(parser.positionalArguments());
414
415 if (parser.isSet(columnWidthOption)) {
416 bool isValidValue = false;
417 const int maxColumnWidth = parser.value(columnWidthOption).toInt(&isValidValue);
418 if (!isValidValue || maxColumnWidth < -1) {
419 options.addError("Error: Invalid value passed to -W. Must be an integer >= -1"_L1);
420 return options;
421 }
422 options.mark(Settings::MaxColumnWidth);
423 options.setMaxColumnWidth(maxColumnWidth);
424 }
425 return options;
426}
427
429 QQmlFormatSettings *settings) const
430{
431 // Perform formatting inplace if --files option is set.
432 const bool hasFiles = !files().isEmpty();
433 QQmlFormatOptions perFileOptions = *this;
434 if (hasFiles)
435 perFileOptions.setIsInplace(true);
436
438 && settings->search(fileName, { m_settingsFile, m_verbose }).isValid())
439 perFileOptions.applySettings(*settings);
440
441 return perFileOptions;
442}
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)