1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include <QCoreApplication>
5#include <QFile>
6#include <QTextStream>
7
8#include <QtQml/private/qqmljslexer_p.h>
9#include <QtQml/private/qqmljsparser_p.h>
10#include <QtQml/private/qqmljsengine_p.h>
11#include <QtQml/private/qqmljsastvisitor_p.h>
12#include <QtQml/private/qqmljsast_p.h>
13#include <QtQmlDom/private/qqmldomitem_p.h>
14#include <QtQmlDom/private/qqmldomexternalitems_p.h>
15#include <QtQmlDom/private/qqmldomtop_p.h>
16#include <QtQmlDom/private/qqmldomoutwriter_p.h>
17#include <QtQmlDom/private/qqmldomlinewriterfactory_p.h>
18
19#if QT_CONFIG(commandlineparser)
20# include <QCommandLineParser>
21#endif
22
23#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
24#include <QtQmlFormat/private/qqmlformatsettings_p.h>
25#include <QtQmlFormat/private/qqmlformatoptions_p.h>
26
27using namespace QQmlJS::Dom;
28
29static void logParsingErrors(const DomItem &fileItem, const QString &filename)
30{
31 fileItem.iterateErrors(
32 visitor: [](const DomItem &, const ErrorMessage &msg) {
33 errorToQDebug(msg);
34 return true;
35 },
36 iterate: true);
37 qWarning().noquote() << "Failed to parse" << filename;
38}
39
40// TODO
41// refactor this workaround. ExternalOWningItem is not recognized as an owning type
42// in ownerAs.
43static std::shared_ptr<ExternalOwningItem> getFileItemOwner(const DomItem &fileItem)
44{
45 std::shared_ptr<ExternalOwningItem> filePtr = nullptr;
46 switch (fileItem.internalKind()) {
47 case DomType::JsFile:
48 filePtr = fileItem.ownerAs<JsFile>();
49 break;
50 default:
51 filePtr = fileItem.ownerAs<QmlFile>();
52 break;
53 }
54 return filePtr;
55}
56
57// TODO refactor
58// Introduce better encapsulation and separation of concerns and move to DOM API
59// returns a DomItem corresponding to the loaded file and bool indicating the validity of the file
60static std::pair<DomItem, bool> parse(const QString &filename)
61{
62 auto envPtr =
63 DomEnvironment::create(loadPaths: QStringList(),
64 options: QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
65 | QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
66 // placeholder for a node
67 // containing metadata (ExternalItemInfo) about the loaded file
68 DomItem fMetadataItem;
69 envPtr->loadFile(file: FileToLoad::fromFileSystem(environment: envPtr, canonicalPath: filename),
70 // callback called when everything is loaded that receives the
71 // loaded external file pair (path, oldValue, newValue)
72 callback: [&fMetadataItem](Path, const DomItem &, const DomItem &extItemInfo) {
73 fMetadataItem = extItemInfo;
74 });
75 auto fItem = fMetadataItem.fileObject();
76 auto filePtr = getFileItemOwner(fileItem: fItem);
77 return { fItem, filePtr && filePtr->isValid() };
78}
79
80static bool parseFile(const QString &filename, const QQmlFormatOptions &options)
81{
82 const auto [fileItem, validFile] = parse(filename);
83 if (!validFile) {
84 logParsingErrors(fileItem, filename);
85 return false;
86 }
87
88 // Turn AST back into source code
89 if (options.isVerbose())
90 qWarning().noquote() << "Dumping" << filename;
91
92 const auto &code = getFileItemOwner(fileItem)->code();
93 auto lwOptions = options.optionsForCode(code);
94 WriteOutChecks checks = WriteOutCheck::Default;
95 //Disable writeOutChecks for some usecases
96 if (options.sortImports() || options.forceEnabled() || options.isMaxColumnWidthSet() || code.size() > 32000
97 || fileItem.internalKind() == DomType::JsFile) {
98 checks = WriteOutCheck::None;
99 }
100
101 bool res = false;
102 if (options.isInplace()) {
103 if (options.isVerbose())
104 qWarning().noquote() << "Writing to file" << filename;
105 FileWriter fw;
106 const unsigned numberOfBackupFiles = 0;
107 res = fileItem.writeOut(path: filename, nBackups: numberOfBackupFiles, opt: lwOptions, fw: &fw, extraChecks: checks);
108 } else {
109 QFile out;
110 if (out.open(stdout, ioFlags: QIODevice::WriteOnly)) {
111 auto lw = createLineWriter(innerSink: [&out](QStringView s) { out.write(data: s.toUtf8()); }, fileName: filename,
112 options: lwOptions);
113 OutWriter ow(*lw);
114 res = fileItem.writeOutForFile(ow, extraChecks: checks);
115 ow.flush();
116 } else {
117 res = false;
118 }
119 }
120 return res;
121}
122
123int main(int argc, char *argv[])
124{
125 QCoreApplication app(argc, argv);
126 QCoreApplication::setApplicationName("qmlformat");
127 QCoreApplication::setApplicationVersion(QT_VERSION_STR);
128
129 QQmlFormatSettings settings(QLatin1String("qmlformat"));
130 const auto &options = QQmlFormatOptions::buildCommandLineOptions(args: app.arguments());
131 if (!options.isValid()) {
132 for (const auto &error : options.errors()) {
133 qWarning().noquote() << error;
134 }
135
136 return -1;
137 }
138
139 if (options.writeDefaultSettingsEnabled())
140 return settings.writeDefaults() ? 0 : -1;
141
142 bool success = true;
143 if (!options.files().isEmpty()) {
144 if (!options.arguments().isEmpty())
145 qWarning() << "Warning: Positional arguments are ignored when -F is used";
146
147 for (const QString &file : options.files()) {
148 Q_ASSERT(!file.isEmpty());
149
150 if (!parseFile(filename: file, options: options.optionsForFile(fileName: file, settings: &settings)))
151 success = false;
152 }
153 } else {
154 for (const QString &file : options.arguments()) {
155 if (!parseFile(filename: file, options: options.optionsForFile(fileName: file, settings: &settings)))
156 success = false;
157 }
158 }
159
160 return success ? 0 : 1;
161}
162

source code of qtdeclarative/tools/qmlformat/qmlformat.cpp