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