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
4#include "qqmlformatoptions_p.h"
5#include "qqmlformatsettings_p.h"
6
7#include <QCommandLineParser>
8#include <QCommandLineOption>
9
10using namespace Qt::StringLiterals;
11
12QQmlFormatOptions::QQmlFormatOptions()
13{
14 setTabsEnabled(false);
15 setNormalizeEnabled(false);
16 setObjectsSpacing(false);
17 setFunctionsSpacing(false);
18 setIndentWidth(4);
19}
20
21QQmlFormatOptions::LineEndings QQmlFormatOptions::detectLineEndings(const QString &code)
22{
23 const QQmlJS::Dom::LineWriterOptions::LineEndings defaultEndings =
24#if defined(Q_OS_WIN)
25 LineEndings::Windows;
26#else
27 LineEndings::Unix;
28#endif
29 // find out current line endings...
30 int newlineIndex = code.indexOf(ch: QChar(u'\n'));
31 int crIndex = code.indexOf(ch: QChar(u'\r'));
32 if (newlineIndex >= 0) {
33 if (crIndex >= 0) {
34 if (crIndex + 1 == newlineIndex)
35 return LineEndings::Windows;
36
37 qWarning().noquote() << "Invalid line ending in file, using default";
38 return defaultEndings;
39 }
40 return LineEndings::Unix;
41 }
42 if (crIndex >= 0) {
43 return LineEndings::OldMacOs;
44 }
45
46 qWarning().noquote() << "Unknown line ending in file, using default";
47 return defaultEndings;
48}
49
50QQmlFormatOptionLineEndings QQmlFormatOptions::parseEndings(const QString &endings)
51{
52 if (endings == u"unix")
53 return Unix;
54 if (endings == u"windows")
55 return Windows;
56 if (endings == u"macos")
57 return OldMacOs;
58 if (endings == u"native")
59 return Native;
60
61 qWarning().noquote() << "Unknown line ending type" << endings << ", using default";
62#if defined(Q_OS_WIN)
63 return Windows;
64#else
65 return Unix;
66#endif
67}
68
69void QQmlFormatOptions::applySettings(const QQmlFormatSettings &settings)
70{
71 // If the options is already set by commandline, don't override it with the values in the .ini
72 // file.
73 if (!isMarked(setting: Settings::IndentWidth)
74 && settings.isSet(name: QQmlFormatSettings::s_indentWidthSetting)) {
75 setIndentWidth(settings.value(name: QQmlFormatSettings::s_indentWidthSetting).toInt());
76 }
77
78 if (!isMarked(setting: Settings::UseTabs) && settings.isSet(name: QQmlFormatSettings::s_useTabsSetting)) {
79 setTabsEnabled(settings.value(name: QQmlFormatSettings::s_useTabsSetting).toBool());
80 }
81
82 if (!isMarked(setting: Settings::MaxColumnWidth)
83 && settings.isSet(name: QQmlFormatSettings::s_maxColumnWidthSetting)) {
84 setMaxColumnWidth(settings.value(name: QQmlFormatSettings::s_maxColumnWidthSetting).toInt());
85 }
86
87 if (!isMarked(setting: Settings::NormalizeOrder)
88 && settings.isSet(name: QQmlFormatSettings::s_normalizeSetting)) {
89 setNormalizeEnabled(settings.value(name: QQmlFormatSettings::s_normalizeSetting).toBool());
90 }
91
92 if (!isMarked(setting: Settings::NewlineType) && settings.isSet(name: QQmlFormatSettings::s_newlineSetting)) {
93 setNewline(QQmlFormatOptions::parseEndings(
94 endings: settings.value(name: QQmlFormatSettings::s_newlineSetting).toString()));
95 }
96
97 if (!isMarked(setting: Settings::ObjectsSpacing)
98 && settings.isSet(name: QQmlFormatSettings::s_objectsSpacingSetting)) {
99 setObjectsSpacing(settings.value(name: QQmlFormatSettings::s_objectsSpacingSetting).toBool());
100 }
101
102 if (!isMarked(setting: Settings::FunctionsSpacing)
103 && settings.isSet(name: QQmlFormatSettings::s_functionsSpacingSetting)) {
104 setFunctionsSpacing(settings.value(name: QQmlFormatSettings::s_functionsSpacingSetting).toBool());
105 }
106
107 if (!isMarked(setting: Settings::SortImports)
108 && settings.isSet(name: QQmlFormatSettings::s_sortImportsSetting)) {
109 setSortImports(settings.value(name: QQmlFormatSettings::s_sortImportsSetting).toBool());
110 }
111}
112
113QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList &args)
114{
115 QQmlFormatOptions options;
116 QCommandLineParser parser;
117 parser.setApplicationDescription(
118 "Formats QML files according to the QML Coding Conventions."_L1);
119 parser.addHelpOption();
120 parser.addVersionOption();
121
122 parser.addOption(
123 commandLineOption: QCommandLineOption({ "V"_L1, "verbose"_L1 },
124 QStringLiteral("Verbose mode. Outputs more detailed information.")));
125
126 QCommandLineOption writeDefaultsOption(
127 QStringList() << "write-defaults"_L1,
128 QLatin1String("Writes defaults settings to .qmlformat.ini and exits (Warning: This "
129 "will overwrite any existing settings and comments!)"_L1));
130 parser.addOption(commandLineOption: writeDefaultsOption);
131
132 QCommandLineOption ignoreSettings(QStringList() << "ignore-settings"_L1,
133 QLatin1String("Ignores all settings files and only takes "
134 "command line options into consideration"_L1));
135 parser.addOption(commandLineOption: ignoreSettings);
136
137 parser.addOption(commandLineOption: QCommandLineOption(
138 { "i"_L1, "inplace"_L1 },
139 QStringLiteral("Edit file in-place instead of outputting to stdout.")));
140
141 parser.addOption(commandLineOption: QCommandLineOption({ "f"_L1, "force"_L1 },
142 QStringLiteral("Continue even if an error has occurred.")));
143
144 parser.addOption(commandLineOption: QCommandLineOption({ "t"_L1, "tabs"_L1 },
145 QStringLiteral("Use tabs instead of spaces.")));
146
147 parser.addOption(commandLineOption: QCommandLineOption({ "w"_L1, "indent-width"_L1 },
148 QStringLiteral("How many spaces are used when indenting."),
149 "width"_L1, "4"_L1));
150
151 QCommandLineOption columnWidthOption(
152 { "W"_L1, "column-width"_L1 },
153 QStringLiteral("Breaks the line into multiple lines if exceedes the specified width. "
154 "Use -1 to disable line wrapping. (default)"),
155 "width"_L1, "-1"_L1);
156 parser.addOption(commandLineOption: columnWidthOption);
157 parser.addOption(commandLineOption: QCommandLineOption({ "n"_L1, "normalize"_L1 },
158 QStringLiteral("Reorders the attributes of the objects "
159 "according to the QML Coding Guidelines.")));
160
161 QCommandLineOption filesOption(
162 { "F"_L1, "files"_L1 }, "Format all files listed in file, in-place"_L1, "file"_L1);
163 parser.addOption(commandLineOption: filesOption);
164
165 parser.addOption(commandLineOption: QCommandLineOption(
166 { "l"_L1, "newline"_L1 },
167 QStringLiteral("Override the new line format to use (native macos unix windows)."),
168 "newline"_L1, "native"_L1));
169
170 parser.addOption(commandLineOption: QCommandLineOption(
171 QStringList() << "objects-spacing"_L1,
172 QStringLiteral("Ensure spaces between objects (only works with normalize option).")));
173
174 parser.addOption(commandLineOption: QCommandLineOption(
175 QStringList() << "functions-spacing"_L1,
176 QStringLiteral("Ensure spaces between functions (only works with normalize option).")));
177
178 parser.addOption(
179 commandLineOption: QCommandLineOption({ "S"_L1, "sort-imports"_L1 },
180 QStringLiteral("Sort imports alphabetically "
181 "(Warning: this might change semantics if a given "
182 "name identifies types in multiple modules!).")));
183 QCommandLineOption semicolonRuleOption(
184 QStringList() << "semicolon-rule"_L1,
185 QStringLiteral("Specify the semicolon rule to use (always, essential).\n"
186 "always: always adds semicolon [default].\n"
187 "essential: adds only when ASI wouldn't be relied on."),
188 "rule"_L1, "always"_L1);
189 parser.addOption(commandLineOption: semicolonRuleOption);
190
191 parser.addPositionalArgument(name: "filenames"_L1, description: "files to be processed by qmlformat"_L1);
192
193 parser.process(arguments: args);
194
195 if (parser.isSet(option: writeDefaultsOption)) {
196 options.setWriteDefaultSettingsEnabled(true);
197 return options;
198 }
199
200 if (parser.positionalArguments().empty() && !parser.isSet(option: filesOption)) {
201 options.addError(newError: "Error: Expected at least one input file."_L1);
202 return options;
203 }
204
205 bool indentWidthOkay = false;
206 const int indentWidth = parser.value(name: "indent-width"_L1).toInt(ok: &indentWidthOkay);
207 if (!indentWidthOkay) {
208 options.addError(newError: "Error: Invalid value passed to -w"_L1);
209 return options;
210 }
211
212 QStringList files;
213 if (!parser.value(name: "files"_L1).isEmpty()) {
214 const QString path = parser.value(name: "files"_L1);
215 QFile file(path);
216 if (!file.open(flags: QIODevice::Text | QIODevice::ReadOnly)) {
217 options.addError(newError: "Error: Could not open file \""_L1 + path + "\" for option -F."_L1);
218 return options;
219 }
220
221 QTextStream in(&file);
222 while (!in.atEnd()) {
223 QString file = in.readLine();
224
225 if (file.isEmpty())
226 continue;
227
228 files.push_back(t: file);
229 }
230
231 if (files.isEmpty()) {
232 options.addError(newError: "Error: File \""_L1 + path + "\" for option -F is empty."_L1);
233 return options;
234 }
235
236 for (const auto &file : std::as_const(t&: files)) {
237 if (!QFile::exists(fileName: file)) {
238 options.addError(newError: "Error: Entry \""_L1 + file + "\" of file \""_L1 + path
239 + "\" passed to option -F could not be found."_L1);
240 return options;
241 }
242 }
243 } else {
244 const auto &args = parser.positionalArguments();
245 for (const auto &file : args) {
246 if (!QFile::exists(fileName: file)) {
247 options.addError(newError: "Error: Could not find file \""_L1 + file + "\"."_L1);
248 return options;
249 }
250 }
251 }
252
253 options.setIsVerbose(parser.isSet(name: "verbose"_L1));
254 options.setIsInplace(parser.isSet(name: "inplace"_L1));
255 options.setForceEnabled(parser.isSet(name: "force"_L1));
256 options.setIgnoreSettingsEnabled(parser.isSet(name: "ignore-settings"_L1));
257
258 if (parser.isSet(name: "tabs"_L1)) {
259 options.mark(setting: Settings::UseTabs);
260 options.setTabsEnabled(true);
261 }
262 if (parser.isSet(name: "normalize"_L1)) {
263 options.mark(setting: Settings::NormalizeOrder);
264 options.setNormalizeEnabled(true);
265 }
266 if (parser.isSet(name: "objects-spacing"_L1)) {
267 options.mark(setting: Settings::ObjectsSpacing);
268 options.setObjectsSpacing(true);
269 }
270 if (parser.isSet(name: "functions-spacing"_L1)) {
271 options.mark(setting: Settings::FunctionsSpacing);
272 options.setFunctionsSpacing(true);
273 }
274 if (parser.isSet(name: "sort-imports"_L1)) {
275 options.mark(setting: Settings::SortImports);
276 options.setSortImports(true);
277 }
278 if (parser.isSet(name: "indent-width"_L1)) {
279 options.mark(setting: Settings::IndentWidth);
280 options.setIndentWidth(indentWidth);
281 }
282
283 if (parser.isSet(name: "newline"_L1)) {
284 options.mark(setting: Settings::NewlineType);
285 options.setNewline(QQmlFormatOptions::parseEndings(endings: parser.value(name: "newline"_L1)));
286 }
287
288 if (parser.isSet(option: semicolonRuleOption)) {
289 options.mark(setting: Settings::SemicolonRule);
290 const auto value = parser.value(option: semicolonRuleOption);
291 if (value == "always"_L1) {
292 options.setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Always);
293 } else if (value == "essential"_L1) {
294 options.setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Essential);
295 } else {
296 options.addError(newError: "Error: Invalid value passed to --semicolon-rule."_L1);
297 return options;
298 }
299 }
300 options.setFiles(files);
301 options.setArguments(parser.positionalArguments());
302
303 if (parser.isSet(option: columnWidthOption)) {
304 bool isValidValue = false;
305 const int maxColumnWidth = parser.value(option: columnWidthOption).toInt(ok: &isValidValue);
306 if (!isValidValue || maxColumnWidth < -1) {
307 options.addError(newError: "Error: Invalid value passed to -W. Must be an integer >= -1"_L1);
308 return options;
309 }
310 options.mark(setting: Settings::MaxColumnWidth);
311 options.setMaxColumnWidth(maxColumnWidth);
312 }
313 return options;
314}
315
316QQmlFormatOptions QQmlFormatOptions::optionsForFile(const QString &fileName,
317 QQmlFormatSettings *settings) const
318{
319 // Perform formatting inplace if --files option is set.
320 const bool hasFiles = !files().isEmpty();
321 QQmlFormatOptions perFileOptions = *this;
322 if (hasFiles)
323 perFileOptions.setIsInplace(true);
324
325 if (!ignoreSettingsEnabled() && settings->search(path: fileName))
326 perFileOptions.applySettings(settings: *settings);
327
328 return perFileOptions;
329}
330

source code of qtdeclarative/src/qmlformat/qqmlformatoptions.cpp