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