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 if (!isMarked(Settings::SortImports)
120 && settings.isSet(QQmlFormatSettings::s_sortImportsSetting)) {
121 setSortImports(settings.value(QQmlFormatSettings::s_sortImportsSetting).toBool());
122 }
123
125 && settings.isSet(QQmlFormatSettings::s_singleLineEmptyObjectsSetting)) {
126 setSingleLineEmptyObjects(settings.value(QQmlFormatSettings::s_singleLineEmptyObjectsSetting).toBool());
127 }
128
129 if (!isMarked(Settings::SemicolonRule)
130 && settings.isSet(QQmlFormatSettings::s_semiColonRuleSetting)) {
131 const auto semicolonRule = parseSemicolonRule(
132 settings.value(QQmlFormatSettings::s_semiColonRuleSetting).toString());
133 if (!semicolonRule.has_value()) {
134 qWarning().noquote() << "Invalid semicolon rule in settings file, using 'always'";
135 setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Always);
136 } else {
137 setSemicolonRule(semicolonRule.value());
138 }
139 }
140}
141
142QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList &args)
143{
144 QQmlFormatOptions options;
145 QCommandLineParser parser;
146 parser.setApplicationDescription(
147 "Formats QML files according to the QML Coding Conventions.\n"_L1
148 "Options below the \"Formatting options\" section can also be set via .qmlformat.ini"_L1
149 " unless --ignore-settings is used"_L1);
150 parser.addHelpOption();
151 parser.addVersionOption();
152
153 //
154 // options that only are set via CLI
155 //
156 parser.addOption(
157 QCommandLineOption({ "V"_L1, "verbose"_L1 },
158 QStringLiteral("Verbose mode. Outputs more detailed information.")));
159
160 QCommandLineOption writeDefaultsOption(
161 QStringList() << "write-defaults"_L1,
162 QLatin1String("Writes defaults settings to .qmlformat.ini and exits (Warning: This "
163 "will overwrite any existing settings and comments!)"_L1));
164 parser.addOption(writeDefaultsOption);
165
166 QCommandLineOption outputOptionsOption(
167 QStringList() << "output-options"_L1,
168 QLatin1String("Output available options and their defaults values in JSON format."_L1));
169 parser.addOption(outputOptionsOption);
170
171 QCommandLineOption ignoreSettings(QStringList() << "ignore-settings"_L1,
172 QLatin1String("Ignores all settings files and only takes "
173 "command line options into consideration"_L1));
174 parser.addOption(ignoreSettings);
175
176 QCommandLineOption filesOption(
177 { "F"_L1, "files"_L1 }, "Format all files listed in file, in-place"_L1, "file"_L1);
178 parser.addOption(filesOption);
179
180
181 QCommandLineOption dryrunOption(
182 QStringList() << "dry-run"_L1,
183 QStringLiteral("Prints the settings file that would be used for this instance."
184 "This is useful to see what settings would be used "
185 "without actually performing anything."));
186 parser.addOption(dryrunOption);
187
188 QCommandLineOption settingsOption(
189 { "s"_L1, "settings"_L1 },
190 QStringLiteral("Use the specified .qmlformat.ini file as the only configuration source."
191 "Overrides any per-directory configuration lookup."),
192 "file"_L1);
193 parser.addOption(settingsOption);
194
195 parser.addOption(QCommandLineOption(
196 { "i"_L1, "inplace"_L1 },
197 QStringLiteral("Edit file in-place instead of outputting to stdout.")));
198
199 // Note the blatant abuse of the option's help text to add a "section marker"
200 // Therefore, this needs to come last. Also, on Windows, the unicode characters seem to cause issues
201 parser.addOption(QCommandLineOption({ "f"_L1, "force"_L1 },
202 #ifdef Q_OS_WINDOWS
203 "Continue even if an error has occurred.\n<><><><><><><><><>\nFormatting options\n<><><><><><><><><>"_L1
204 #else
205 u"Continue even if an error has occurred.\n♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦\nFormatting options\n♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦♦"_s
206 #endif
207 ));
208
209 //
210 // options that can be configured by qmlformat.ini
211 //
212
213 parser.addOption(QCommandLineOption({ "t"_L1, "tabs"_L1 },
214 QStringLiteral("Use tabs instead of spaces.")));
215
216 parser.addOption(QCommandLineOption({ "w"_L1, "indent-width"_L1 },
217 QStringLiteral("How many spaces are used when indenting."),
218 "width"_L1, "4"_L1));
219
220 QCommandLineOption columnWidthOption(
221 { "W"_L1, "column-width"_L1 },
222 QStringLiteral("Breaks the line into multiple lines if exceedes the specified width. "
223 "Use -1 to disable line wrapping. (default)"),
224 "width"_L1, "-1"_L1);
225 parser.addOption(columnWidthOption);
226 parser.addOption(QCommandLineOption({ "n"_L1, "normalize"_L1 },
227 QStringLiteral("Reorders the attributes of the objects "
228 "according to the QML Coding Guidelines.")));
229
230
231 parser.addOption(QCommandLineOption(
232 { "l"_L1, "newline"_L1 },
233 QStringLiteral("Override the new line format to use (native macos unix windows)."),
234 "newline"_L1, "native"_L1));
235
236 parser.addOption(QCommandLineOption(
237 QStringList() << "objects-spacing"_L1,
238 QStringLiteral("Ensure spaces between objects (only works with normalize option).")));
239
240 parser.addOption(QCommandLineOption(
241 QStringList() << "functions-spacing"_L1,
242 QStringLiteral("Ensure spaces between functions (only works with normalize option).")));
243
244 parser.addOption(
245 QCommandLineOption({ "S"_L1, "sort-imports"_L1 },
246 QStringLiteral("Sort imports alphabetically "
247 "(Warning: this might change semantics if a given "
248 "name identifies types in multiple modules!).")));
249
250 parser.addOption(QCommandLineOption(
251 QStringList() << "single-line-empty-objects"_L1,
252 QStringLiteral("Write empty objects on a single line (only works with normalize option).")));
253
254 QCommandLineOption semicolonRuleOption(
255 QStringList() << "semicolon-rule"_L1,
256 QStringLiteral("Specify the semicolon rule to use (always, essential).\n"
257 "always: always adds semicolon [default].\n"
258 "essential: adds only when ASI wouldn't be relied on."),
259 "rule"_L1, "always"_L1);
260 parser.addOption(semicolonRuleOption);
261
262 parser.addPositionalArgument("filenames"_L1, "files to be processed by qmlformat"_L1);
263
264 parser.process(args);
265
266 if (parser.isSet(writeDefaultsOption)) {
268 return options;
269 }
270
271 if (parser.isSet(outputOptionsOption)) {
273 return options;
274 }
275
276 if (parser.positionalArguments().empty() && !parser.isSet(filesOption)) {
277 options.addError("Error: Expected at least one input file."_L1);
278 return options;
279 }
280
281 bool indentWidthOkay = false;
282 const int indentWidth = parser.value("indent-width"_L1).toInt(&indentWidthOkay);
283 if (!indentWidthOkay) {
284 options.addError("Error: Invalid value passed to -w"_L1);
285 return options;
286 }
287
288 QStringList files;
289 if (!parser.value("files"_L1).isEmpty()) {
290 const QString path = parser.value("files"_L1);
291 QFile file(path);
292 if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
293 options.addError("Error: Could not open file \""_L1 + path + "\" for option -F."_L1);
294 return options;
295 }
296
297 QTextStream in(&file);
298 while (!in.atEnd()) {
299 QString file = in.readLine();
300
301 if (file.isEmpty())
302 continue;
303
304 files.push_back(file);
305 }
306
307 if (files.isEmpty()) {
308 options.addError("Error: File \""_L1 + path + "\" for option -F is empty."_L1);
309 return options;
310 }
311
312 for (const auto &file : std::as_const(files)) {
313 if (!QFile::exists(file)) {
314 options.addError("Error: Entry \""_L1 + file + "\" of file \""_L1 + path
315 + "\" passed to option -F could not be found."_L1);
316 return options;
317 }
318 }
319 } else {
320 const auto &args = parser.positionalArguments();
321 for (const auto &file : args) {
322 if (!QFile::exists(file)) {
323 options.addError("Error: Could not find file \""_L1 + file + "\"."_L1);
324 return options;
325 }
326 }
327 }
328
329 options.setDryRun(parser.isSet(dryrunOption));
330 options.setIsVerbose(parser.isSet("verbose"_L1));
331 options.setIsInplace(parser.isSet("inplace"_L1));
332 options.setForceEnabled(parser.isSet("force"_L1));
333 options.setIgnoreSettingsEnabled(parser.isSet("ignore-settings"_L1));
334
335 if (parser.isSet("tabs"_L1)) {
336 options.mark(Settings::UseTabs);
337 options.setTabsEnabled(true);
338 }
339 if (parser.isSet("normalize"_L1)) {
340 options.mark(Settings::NormalizeOrder);
341 options.setNormalizeEnabled(true);
342 }
343 if (parser.isSet("objects-spacing"_L1)) {
344 options.mark(Settings::ObjectsSpacing);
345 options.setObjectsSpacing(true);
346 }
347 if (parser.isSet("functions-spacing"_L1)) {
348 options.mark(Settings::FunctionsSpacing);
349 options.setFunctionsSpacing(true);
350 }
351 if (parser.isSet("sort-imports"_L1)) {
352 options.mark(Settings::SortImports);
353 options.setSortImports(true);
354 }
355 if (parser.isSet("single-line-empty-objects"_L1)) {
358 }
359 if (parser.isSet("indent-width"_L1)) {
360 options.mark(Settings::IndentWidth);
361 options.setIndentWidth(indentWidth);
362 }
363
364 if (parser.isSet("newline"_L1)) {
365 options.mark(Settings::NewlineType);
366 options.setNewline(QQmlFormatOptions::parseEndings(parser.value("newline"_L1)));
367 }
368
369 if (parser.isSet(settingsOption)) {
370 options.mark(Settings::SettingsFile);
371 const auto value = parser.value(settingsOption);
372 if (value.isEmpty()) {
373 options.addError("Error: No settings file specified for option -s."_L1);
374 return options;
375 }
376 if (!QFile::exists(value)) {
377 options.addError("Error: Could not find file \""_L1 + value + "\"."_L1);
378 return options;
379 }
380 options.setSettingsFile(value);
381 }
382
383 if (parser.isSet(semicolonRuleOption)) {
384 options.mark(Settings::SemicolonRule);
385 const auto value = parser.value(semicolonRuleOption);
386 auto semicolonRule = parseSemicolonRule(value);
387 if (!semicolonRule.has_value()) {
388 options.addError("Error: Invalid value passed to --semicolon-rule. Must be 'always' or 'essential'."_L1);
389 return options;
390 }
391 options.setSemicolonRule(semicolonRule.value());
392 }
393 options.setFiles(files);
394 options.setArguments(parser.positionalArguments());
395
396 if (parser.isSet(columnWidthOption)) {
397 bool isValidValue = false;
398 const int maxColumnWidth = parser.value(columnWidthOption).toInt(&isValidValue);
399 if (!isValidValue || maxColumnWidth < -1) {
400 options.addError("Error: Invalid value passed to -W. Must be an integer >= -1"_L1);
401 return options;
402 }
403 options.mark(Settings::MaxColumnWidth);
404 options.setMaxColumnWidth(maxColumnWidth);
405 }
406 return options;
407}
408
410 QQmlFormatSettings *settings) const
411{
412 // Perform formatting inplace if --files option is set.
413 const bool hasFiles = !files().isEmpty();
414 QQmlFormatOptions perFileOptions = *this;
415 if (hasFiles)
416 perFileOptions.setIsInplace(true);
417
418 if (!ignoreSettingsEnabled()
419 && settings->search(fileName, { m_settingsFile, m_verbose }).isValid())
420 perFileOptions.applySettings(*settings);
421
422 return perFileOptions;
423}
void setTabsEnabled(bool tabs)
void setSingleLineEmptyObjects(bool singleLineEmptyObjects)
void setNormalizeEnabled(bool normalize)
void setOutputOptionsEnabled(bool newOutputOptions)
void setMaxColumnWidth(int width)
void applySettings(const QQmlFormatSettings &settings)
void setSortImports(bool sort)
void setFunctionsSpacing(bool spacing)
void setIndentWidth(int width)
void setObjectsSpacing(bool spacing)
void setIsInplace(bool newInplace)
void setWriteDefaultSettingsEnabled(bool newWriteDefaultSettings)
void setDryRun(bool newDryRun)
QQmlFormatOptions optionsForFile(const QString &fileName, QQmlFormatSettings *settings) const
std::optional< QQmlJS::Dom::LineWriterOptions::SemicolonRule > parseSemicolonRule(const QString &value)