1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QCoreApplication>
30#include <QFile>
31
32#include <QtQml/private/qqmljslexer_p.h>
33#include <QtQml/private/qqmljsparser_p.h>
34#include <QtQml/private/qqmljsengine_p.h>
35#include <QtQml/private/qqmljsastvisitor_p.h>
36#include <QtQml/private/qqmljsast_p.h>
37
38#if QT_CONFIG(commandlineparser)
39#include <QCommandLineParser>
40#endif
41
42#include "commentastvisitor.h"
43#include "dumpastvisitor.h"
44#include "restructureastvisitor.h"
45
46bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImports, bool force, const QString& newline)
47{
48 QFile file(filename);
49
50 if (!file.open(flags: QIODevice::Text | QIODevice::ReadOnly)) {
51 qWarning().noquote() << "Failed to open" << filename << "for reading.";
52 return false;
53 }
54
55 QString code = QString::fromUtf8(str: file.readAll());
56 file.close();
57
58 QQmlJS::Engine engine;
59 QQmlJS::Lexer lexer(&engine);
60
61 lexer.setCode(code, lineno: 1, qmlMode: true);
62 QQmlJS::Parser parser(&engine);
63
64 bool success = parser.parse();
65
66 if (!success) {
67 const auto diagnosticMessages = parser.diagnosticMessages();
68 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
69 qWarning().noquote() << QString::fromLatin1("%1:%2 : %3")
70 .arg(filename).arg(m.loc.startLine).arg(m.message);
71 }
72
73 qWarning().noquote() << "Failed to parse" << filename;
74 return false;
75 }
76
77 // Try to attach comments to AST nodes
78 CommentAstVisitor comment(&engine, parser.rootNode());
79
80 if (verbose)
81 qWarning().noquote() << comment.attachedComments().size() << "comment(s) attached.";
82
83 if (verbose) {
84 int orphaned = 0;
85
86 for (const auto& orphanList : comment.orphanComments().values())
87 orphaned += orphanList.size();
88
89 qWarning().noquote() << orphaned << "comments are orphans.";
90 }
91
92 if (verbose && sortImports)
93 qWarning().noquote() << "Sorting imports";
94
95 // Do the actual restructuring
96 RestructureAstVisitor restructure(parser.rootNode(), sortImports);
97
98 // Turn AST back into source code
99 if (verbose)
100 qWarning().noquote() << "Dumping" << filename;
101
102 DumpAstVisitor dump(&engine, parser.rootNode(), &comment);
103
104 QString dumpCode = dump.toString();
105
106 lexer.setCode(code: dumpCode, lineno: 1, qmlMode: true);
107
108 bool dumpSuccess = parser.parse();
109
110 if (!dumpSuccess) {
111 if (verbose) {
112 const auto diagnosticMessages = parser.diagnosticMessages();
113 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
114 qWarning().noquote() << QString::fromLatin1("<formatted>:%2 : %3")
115 .arg(m.loc.startLine).arg(m.message);
116 }
117 }
118
119 qWarning().noquote() << "Failed to parse formatted code.";
120 }
121
122 if (dump.error() || !dumpSuccess) {
123 if (force) {
124 qWarning().noquote() << "An error has occurred. The output may not be reliable.";
125 } else {
126 qWarning().noquote() << "An error has occurred. Aborting.";
127 return false;
128 }
129 }
130
131
132 const bool native = newline == "native";
133
134 if (!native) {
135 if (newline == "macos") {
136 dumpCode = dumpCode.replace(before: "\n",after: "\r");
137 } else if (newline == "windows") {
138 dumpCode = dumpCode.replace(before: "\n", after: "\r\n");
139 } else if (newline == "unix") {
140 // Nothing needs to be done for unix line-endings
141 } else {
142 qWarning().noquote() << "Unknown line ending type" << newline;
143 return false;
144 }
145 }
146
147 if (inplace) {
148 if (verbose)
149 qWarning().noquote() << "Writing to file" << filename;
150
151 if (!file.open(flags: native ? QIODevice::WriteOnly | QIODevice::Text : QIODevice::WriteOnly))
152 {
153 qWarning().noquote() << "Failed to open" << filename << "for writing";
154 return false;
155 }
156
157 file.write(data: dumpCode.toUtf8());
158 file.close();
159 } else {
160 QFile out;
161 out.open(stdout, ioFlags: QIODevice::WriteOnly);
162 out.write(data: dumpCode.toUtf8());
163 }
164
165 return true;
166}
167
168int main(int argc, char *argv[])
169{
170 QCoreApplication app(argc, argv);
171 QCoreApplication::setApplicationName("qmlformat");
172 QCoreApplication::setApplicationVersion("1.0");
173
174 bool success = true;
175#if QT_CONFIG(commandlineparser)
176 QCommandLineParser parser;
177 parser.setApplicationDescription("Formats QML files according to the QML Coding Conventions.");
178 parser.addHelpOption();
179 parser.addVersionOption();
180
181 parser.addOption(commandLineOption: QCommandLineOption({"V", "verbose"},
182 QStringLiteral("Verbose mode. Outputs more detailed information.")));
183
184 parser.addOption(commandLineOption: QCommandLineOption({"n", "no-sort"},
185 QStringLiteral("Do not sort imports.")));
186
187 parser.addOption(commandLineOption: QCommandLineOption({"i", "inplace"},
188 QStringLiteral("Edit file in-place instead of outputting to stdout.")));
189
190 parser.addOption(commandLineOption: QCommandLineOption({"f", "force"},
191 QStringLiteral("Continue even if an error has occurred.")));
192
193 parser.addOption(commandLineOption: QCommandLineOption({"l", "newline"},
194 QStringLiteral("Override the new line format to use (native macos unix windows)."),
195 "newline", "native"));
196
197 parser.addPositionalArgument(name: "filenames", description: "files to be processed by qmlformat");
198
199 parser.process(app);
200
201 const auto positionalArguments = parser.positionalArguments();
202
203 if (positionalArguments.isEmpty())
204 parser.showHelp(exitCode: -1);
205
206 if (!parser.isSet(name: "inplace") && parser.value(name: "newline") != "native") {
207 qWarning() << "Error: The -l option can only be used with -i";
208 return -1;
209 }
210
211 for (const QString& file: parser.positionalArguments()) {
212 if (!parseFile(filename: file, inplace: parser.isSet(name: "inplace"), verbose: parser.isSet(name: "verbose"), sortImports: !parser.isSet(name: "no-sort"), force: parser.isSet(name: "force"), newline: parser.value(name: "newline")))
213 success = false;
214 }
215#endif
216
217 return success ? 0 : 1;
218}
219

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