1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module 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 <private/qqmljsengine_p.h> |
30 | #include <private/qqmljslexer_p.h> |
31 | #include <private/qqmljsparser_p.h> |
32 | #include <QtCore/QCoreApplication> |
33 | #include <QtCore/QStringList> |
34 | #include <QtCore/QFile> |
35 | #include <QtCore/QFileInfo> |
36 | #include <QtCore/QDir> |
37 | #include <iostream> |
38 | #include <cstdlib> |
39 | |
40 | QT_BEGIN_NAMESPACE |
41 | |
42 | // |
43 | // QML/JS minifier |
44 | // |
45 | namespace QQmlJS { |
46 | |
47 | enum RegExpFlag { |
48 | Global = 0x01, |
49 | IgnoreCase = 0x02, |
50 | Multiline = 0x04 |
51 | }; |
52 | |
53 | |
54 | class QmlminLexer: protected Lexer, public Directives |
55 | { |
56 | QQmlJS::Engine _engine; |
57 | QString _fileName; |
58 | QString _directives; |
59 | |
60 | protected: |
61 | QVector<int> _stateStack; |
62 | QList<int> _tokens; |
63 | QList<QString> _tokenStrings; |
64 | int yytoken = -1; |
65 | QString yytokentext; |
66 | |
67 | void lex() { |
68 | if (_tokens.isEmpty()) { |
69 | _tokens.append(t: Lexer::lex()); |
70 | _tokenStrings.append(t: tokenText()); |
71 | } |
72 | |
73 | yytoken = _tokens.takeFirst(); |
74 | yytokentext = _tokenStrings.takeFirst(); |
75 | } |
76 | |
77 | int lookaheadToken() |
78 | { |
79 | if (yytoken < 0) |
80 | lex(); |
81 | return yytoken; |
82 | } |
83 | |
84 | void pushToken(int token) |
85 | { |
86 | _tokens.prepend(t: yytoken); |
87 | _tokenStrings.prepend(t: yytokentext); |
88 | yytoken = token; |
89 | yytokentext = QString(); |
90 | } |
91 | |
92 | public: |
93 | QmlminLexer() |
94 | : Lexer(&_engine), _stateStack(128) {} |
95 | virtual ~QmlminLexer() {} |
96 | |
97 | QString fileName() const { return _fileName; } |
98 | |
99 | bool operator()(const QString &fileName, const QString &code) |
100 | { |
101 | int startToken = T_FEED_JS_SCRIPT; |
102 | const QFileInfo fileInfo(fileName); |
103 | if (fileInfo.suffix().toLower() == QLatin1String("qml" )) |
104 | startToken = T_FEED_UI_PROGRAM; |
105 | setCode(code, /*line = */ lineno: 1, /*qmlMode = */ startToken == T_FEED_UI_PROGRAM); |
106 | _fileName = fileName; |
107 | _directives.clear(); |
108 | return parse(startToken); |
109 | } |
110 | |
111 | QString directives() |
112 | { |
113 | return _directives; |
114 | } |
115 | |
116 | // |
117 | // Handle the .pragma/.import directives |
118 | // |
119 | void pragmaLibrary() override |
120 | { |
121 | _directives += QLatin1String(".pragma library\n" ); |
122 | } |
123 | |
124 | void importFile(const QString &jsfile, const QString &module, int line, int column) override |
125 | { |
126 | _directives += QLatin1String(".import" ); |
127 | _directives += QLatin1Char('"'); |
128 | _directives += quote(string: jsfile); |
129 | _directives += QLatin1Char('"'); |
130 | _directives += QLatin1String("as " ); |
131 | _directives += module; |
132 | _directives += QLatin1Char('\n'); |
133 | Q_UNUSED(line); |
134 | Q_UNUSED(column); |
135 | } |
136 | |
137 | void importModule(const QString &uri, const QString &version, const QString &module, int line, int column) override |
138 | { |
139 | _directives += QLatin1String(".import " ); |
140 | _directives += uri; |
141 | _directives += QLatin1Char(' '); |
142 | _directives += version; |
143 | _directives += QLatin1String(" as " ); |
144 | _directives += module; |
145 | _directives += QLatin1Char('\n'); |
146 | Q_UNUSED(line); |
147 | Q_UNUSED(column); |
148 | } |
149 | |
150 | protected: |
151 | virtual bool parse(int startToken) = 0; |
152 | |
153 | static QString quote(const QString &string) |
154 | { |
155 | QString quotedString; |
156 | for (const QChar &ch : string) { |
157 | if (ch == QLatin1Char('"')) |
158 | quotedString += QLatin1String("\\\"" ); |
159 | else { |
160 | if (ch == QLatin1Char('\\')) quotedString += QLatin1String("\\\\" ); |
161 | else if (ch == QLatin1Char('\"')) quotedString += QLatin1String("\\\"" ); |
162 | else if (ch == QLatin1Char('\b')) quotedString += QLatin1String("\\b" ); |
163 | else if (ch == QLatin1Char('\f')) quotedString += QLatin1String("\\f" ); |
164 | else if (ch == QLatin1Char('\n')) quotedString += QLatin1String("\\n" ); |
165 | else if (ch == QLatin1Char('\r')) quotedString += QLatin1String("\\r" ); |
166 | else if (ch == QLatin1Char('\t')) quotedString += QLatin1String("\\t" ); |
167 | else if (ch == QLatin1Char('\v')) quotedString += QLatin1String("\\v" ); |
168 | else if (ch == QLatin1Char('\0')) quotedString += QLatin1String("\\0" ); |
169 | else quotedString += ch; |
170 | } |
171 | } |
172 | return quotedString; |
173 | } |
174 | |
175 | bool isIdentChar(const QChar &ch) const |
176 | { |
177 | if (ch.isLetterOrNumber()) |
178 | return true; |
179 | else if (ch == QLatin1Char('_') || ch == QLatin1Char('$')) |
180 | return true; |
181 | return false; |
182 | } |
183 | |
184 | bool isRegExpRule(int ruleno) const |
185 | { |
186 | return ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 || |
187 | ruleno == J_SCRIPT_REGEXPLITERAL_RULE2; |
188 | } |
189 | |
190 | void handleLookaheads(int ruleno) { |
191 | if (ruleno == J_SCRIPT_EXPRESSIONSTATEMENTLOOKAHEAD_RULE) { |
192 | int token = lookaheadToken(); |
193 | if (token == T_LBRACE) |
194 | pushToken(token: T_FORCE_BLOCK); |
195 | else if (token == T_FUNCTION || token == T_CLASS || token == T_LET || token == T_CONST) |
196 | pushToken(token: T_FORCE_DECLARATION); |
197 | } else if (ruleno == J_SCRIPT_CONCISEBODYLOOKAHEAD_RULE) { |
198 | int token = lookaheadToken(); |
199 | if (token == T_LBRACE) |
200 | pushToken(token: T_FORCE_BLOCK); |
201 | } else if (ruleno == J_SCRIPT_EXPORTDECLARATIONLOOKAHEAD_RULE) { |
202 | int token = lookaheadToken(); |
203 | if (token == T_FUNCTION || token == T_CLASS) |
204 | pushToken(token: T_FORCE_DECLARATION); |
205 | } |
206 | } |
207 | |
208 | bool scanRestOfRegExp(int ruleno, QString *restOfRegExp) |
209 | { |
210 | if (! scanRegExp(prefix: ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 ? Lexer::NoPrefix : Lexer::EqualPrefix)) |
211 | return false; |
212 | |
213 | *restOfRegExp = regExpPattern(); |
214 | if (ruleno == J_SCRIPT_REGEXPLITERAL_RULE2) { |
215 | Q_ASSERT(! restOfRegExp->isEmpty()); |
216 | Q_ASSERT(restOfRegExp->at(0) == QLatin1Char('=')); |
217 | *restOfRegExp = restOfRegExp->mid(position: 1); // strip the prefix |
218 | } |
219 | *restOfRegExp += QLatin1Char('/'); |
220 | const RegExpFlag flags = (RegExpFlag) regExpFlags(); |
221 | if (flags & Global) |
222 | *restOfRegExp += QLatin1Char('g'); |
223 | if (flags & IgnoreCase) |
224 | *restOfRegExp += QLatin1Char('i'); |
225 | if (flags & Multiline) |
226 | *restOfRegExp += QLatin1Char('m'); |
227 | |
228 | if (regExpFlags() == 0) { |
229 | // Add an extra space after the regexp literal delimiter (aka '/'). |
230 | // This will avoid possible problems when pasting tokens like `instanceof' |
231 | // after the regexp literal. |
232 | *restOfRegExp += QLatin1Char(' '); |
233 | } |
234 | return true; |
235 | } |
236 | }; |
237 | |
238 | |
239 | class Minify: public QmlminLexer |
240 | { |
241 | QString _minifiedCode; |
242 | int _maxWidth; |
243 | int _width; |
244 | |
245 | public: |
246 | Minify(int maxWidth); |
247 | |
248 | QString minifiedCode() const; |
249 | |
250 | protected: |
251 | void append(const QString &s); |
252 | bool parse(int startToken) override; |
253 | void escape(const QChar &ch, QString *out); |
254 | }; |
255 | |
256 | Minify::Minify(int maxWidth) |
257 | : _maxWidth(maxWidth), _width(0) |
258 | { |
259 | } |
260 | |
261 | QString Minify::minifiedCode() const |
262 | { |
263 | return _minifiedCode; |
264 | } |
265 | |
266 | void Minify::append(const QString &s) |
267 | { |
268 | if (!s.isEmpty()) { |
269 | if (_maxWidth) { |
270 | // Prefer not to exceed the maximum chars per line (but don't break up segments) |
271 | int segmentLength = s.count(); |
272 | if (_width && ((_width + segmentLength) > _maxWidth)) { |
273 | _minifiedCode.append(c: QLatin1Char('\n')); |
274 | _width = 0; |
275 | } |
276 | |
277 | _width += segmentLength; |
278 | } |
279 | |
280 | _minifiedCode.append(s); |
281 | } |
282 | } |
283 | |
284 | void Minify::escape(const QChar &ch, QString *out) |
285 | { |
286 | out->append(s: QLatin1String("\\u" )); |
287 | const QString hx = QString::number(ch.unicode(), base: 16); |
288 | switch (hx.length()) { |
289 | case 1: out->append(s: QLatin1String("000" )); break; |
290 | case 2: out->append(s: QLatin1String("00" )); break; |
291 | case 3: out->append(c: QLatin1Char('0')); break; |
292 | case 4: break; |
293 | default: Q_ASSERT(!"unreachable" ); |
294 | } |
295 | out->append(s: hx); |
296 | } |
297 | |
298 | bool Minify::parse(int startToken) |
299 | { |
300 | int yyaction = 0; |
301 | int yytos = -1; |
302 | QString assembled; |
303 | |
304 | _minifiedCode.clear(); |
305 | _tokens.append(t: startToken); |
306 | _tokenStrings.append(t: QString()); |
307 | |
308 | if (startToken == T_FEED_JS_SCRIPT) { |
309 | // parse optional pragma directive |
310 | DiagnosticMessage error; |
311 | if (scanDirectives(directives: this, error: &error)) { |
312 | // append the scanned directives to the minifier code. |
313 | append(s: directives()); |
314 | |
315 | _tokens.append(t: tokenKind()); |
316 | _tokenStrings.append(t: tokenText()); |
317 | } else { |
318 | std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' |
319 | << tokenStartColumn() << ": syntax error" << std::endl; |
320 | return false; |
321 | } |
322 | } |
323 | |
324 | do { |
325 | if (++yytos == _stateStack.size()) |
326 | _stateStack.resize(asize: _stateStack.size() * 2); |
327 | |
328 | _stateStack[yytos] = yyaction; |
329 | |
330 | again: |
331 | if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) |
332 | lex(); |
333 | |
334 | yyaction = t_action(yyaction, yytoken); |
335 | if (yyaction > 0) { |
336 | if (yyaction == ACCEPT_STATE) { |
337 | --yytos; |
338 | if (!assembled.isEmpty()) |
339 | append(s: assembled); |
340 | return true; |
341 | } |
342 | |
343 | const QChar lastChar = assembled.isEmpty() ? (_minifiedCode.isEmpty() ? QChar() |
344 | : _minifiedCode.at(i: _minifiedCode.length() - 1)) |
345 | : assembled.at(i: assembled.length() - 1); |
346 | |
347 | if (yytoken == T_SEMICOLON) { |
348 | assembled += QLatin1Char(';'); |
349 | |
350 | append(s: assembled); |
351 | assembled.clear(); |
352 | |
353 | } else if (yytoken == T_PLUS || yytoken == T_MINUS || yytoken == T_PLUS_PLUS || yytoken == T_MINUS_MINUS) { |
354 | if (lastChar == QLatin1Char(spell[yytoken][0])) { |
355 | // don't merge unary signs, additive expressions and postfix/prefix increments. |
356 | assembled += QLatin1Char(' '); |
357 | } |
358 | |
359 | assembled += QLatin1String(spell[yytoken]); |
360 | |
361 | } else if (yytoken == T_NUMERIC_LITERAL) { |
362 | if (isIdentChar(ch: lastChar)) |
363 | assembled += QLatin1Char(' '); |
364 | |
365 | if (yytokentext.startsWith(c: '.')) |
366 | assembled += QLatin1Char('0'); |
367 | |
368 | assembled += yytokentext; |
369 | |
370 | if (assembled.endsWith(c: QLatin1Char('.'))) |
371 | assembled += QLatin1Char('0'); |
372 | |
373 | } else if (yytoken == T_IDENTIFIER) { |
374 | QString identifier = yytokentext; |
375 | |
376 | if (classify(identifier.constData(), identifier.size(), qmlMode()) != T_IDENTIFIER) { |
377 | // the unescaped identifier is a keyword. In this case just replace |
378 | // the last character of the identifier with it escape sequence. |
379 | const QChar ch = identifier.at(i: identifier.length() - 1); |
380 | identifier.chop(n: 1); |
381 | escape(ch, out: &identifier); |
382 | } |
383 | |
384 | if (isIdentChar(ch: lastChar)) |
385 | assembled += QLatin1Char(' '); |
386 | |
387 | assembled += identifier; |
388 | |
389 | } else if (yytoken == T_STRING_LITERAL || yytoken == T_MULTILINE_STRING_LITERAL) { |
390 | assembled += QLatin1Char('"'); |
391 | assembled += quote(string: yytokentext); |
392 | assembled += QLatin1Char('"'); |
393 | } else { |
394 | if (isIdentChar(ch: lastChar)) { |
395 | if (! yytokentext.isEmpty()) { |
396 | const QChar ch = yytokentext.at(i: 0); |
397 | if (isIdentChar(ch)) |
398 | assembled += QLatin1Char(' '); |
399 | } |
400 | } |
401 | assembled += yytokentext; |
402 | } |
403 | yytoken = -1; |
404 | } else if (yyaction < 0) { |
405 | const int ruleno = -yyaction - 1; |
406 | yytos -= rhs[ruleno]; |
407 | |
408 | handleLookaheads(ruleno); |
409 | |
410 | if (isRegExpRule(ruleno)) { |
411 | QString restOfRegExp; |
412 | |
413 | if (! scanRestOfRegExp(ruleno, restOfRegExp: &restOfRegExp)) |
414 | break; // break the loop, it wil report a syntax error |
415 | |
416 | assembled += restOfRegExp; |
417 | } |
418 | yyaction = nt_action(_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT); |
419 | } |
420 | } while (yyaction); |
421 | |
422 | const int yyerrorstate = _stateStack[yytos]; |
423 | |
424 | // automatic insertion of `;' |
425 | if (yytoken != -1 && ((t_action(yyerrorstate, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken)) |
426 | || t_action(yyerrorstate, T_COMPATIBILITY_SEMICOLON))) { |
427 | _tokens.prepend(t: yytoken); |
428 | _tokenStrings.prepend(t: yytokentext); |
429 | yyaction = yyerrorstate; |
430 | yytoken = T_SEMICOLON; |
431 | goto again; |
432 | } |
433 | |
434 | std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' << tokenStartColumn() |
435 | << ": syntax error" << std::endl; |
436 | return false; |
437 | } |
438 | |
439 | |
440 | class Tokenize: public QmlminLexer |
441 | { |
442 | QStringList _minifiedCode; |
443 | |
444 | public: |
445 | Tokenize() {} |
446 | |
447 | QStringList tokenStream() const; |
448 | |
449 | protected: |
450 | bool parse(int startToken) override; |
451 | }; |
452 | |
453 | QStringList Tokenize::tokenStream() const |
454 | { |
455 | return _minifiedCode; |
456 | } |
457 | |
458 | bool Tokenize::parse(int startToken) |
459 | { |
460 | int yyaction = 0; |
461 | int yytos = -1; |
462 | |
463 | _minifiedCode.clear(); |
464 | _tokens.append(t: startToken); |
465 | _tokenStrings.append(t: QString()); |
466 | |
467 | if (startToken == T_FEED_JS_SCRIPT) { |
468 | // parse optional pragma directive |
469 | DiagnosticMessage error; |
470 | if (scanDirectives(directives: this, error: &error)) { |
471 | // append the scanned directives as one token to |
472 | // the token stream. |
473 | _minifiedCode.append(t: directives()); |
474 | |
475 | _tokens.append(t: tokenKind()); |
476 | _tokenStrings.append(t: tokenText()); |
477 | } else { |
478 | std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' |
479 | << tokenStartColumn() << ": syntax error" << std::endl; |
480 | return false; |
481 | } |
482 | } |
483 | |
484 | do { |
485 | if (++yytos == _stateStack.size()) |
486 | _stateStack.resize(asize: _stateStack.size() * 2); |
487 | |
488 | _stateStack[yytos] = yyaction; |
489 | |
490 | again: |
491 | if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) |
492 | lex(); |
493 | |
494 | yyaction = t_action(yyaction, yytoken); |
495 | if (yyaction > 0) { |
496 | if (yyaction == ACCEPT_STATE) { |
497 | --yytos; |
498 | return true; |
499 | } |
500 | |
501 | if (yytoken == T_SEMICOLON) |
502 | _minifiedCode += QLatin1String(";" ); |
503 | else |
504 | _minifiedCode += yytokentext; |
505 | |
506 | yytoken = -1; |
507 | } else if (yyaction < 0) { |
508 | const int ruleno = -yyaction - 1; |
509 | yytos -= rhs[ruleno]; |
510 | |
511 | handleLookaheads(ruleno); |
512 | |
513 | if (isRegExpRule(ruleno)) { |
514 | QString restOfRegExp; |
515 | |
516 | if (! scanRestOfRegExp(ruleno, restOfRegExp: &restOfRegExp)) |
517 | break; // break the loop, it wil report a syntax error |
518 | |
519 | _minifiedCode.last().append(s: restOfRegExp); |
520 | } |
521 | |
522 | yyaction = nt_action(_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT); |
523 | } |
524 | } while (yyaction); |
525 | |
526 | const int yyerrorstate = _stateStack[yytos]; |
527 | |
528 | // automatic insertion of `;' |
529 | if (yytoken != -1 && ((t_action(yyerrorstate, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken)) |
530 | || t_action(yyerrorstate, T_COMPATIBILITY_SEMICOLON))) { |
531 | _tokens.prepend(t: yytoken); |
532 | _tokenStrings.prepend(t: yytokentext); |
533 | yyaction = yyerrorstate; |
534 | yytoken = T_SEMICOLON; |
535 | goto again; |
536 | } |
537 | |
538 | std::cerr << qPrintable(fileName()) << ':' << tokenStartLine() << ':' |
539 | << tokenStartColumn() << ": syntax error" << std::endl; |
540 | return false; |
541 | } |
542 | |
543 | } // end of QQmlJS namespace |
544 | |
545 | static void usage(bool showHelp = false) |
546 | { |
547 | std::cerr << "Usage: qmlmin [options] file" << std::endl; |
548 | |
549 | if (showHelp) { |
550 | std::cerr << " Removes comments and layout characters" << std::endl |
551 | << " The options are:" << std::endl |
552 | << " -o<file> write output to file rather than stdout" << std::endl |
553 | << " -v --verify-only just run the verifier, no output" << std::endl |
554 | << " -w<width> restrict line characters to width" << std::endl |
555 | << " -h display this output" << std::endl; |
556 | } |
557 | } |
558 | |
559 | int runQmlmin(int argc, char *argv[]) |
560 | { |
561 | QCoreApplication app(argc, argv); |
562 | QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); |
563 | |
564 | const QStringList args = app.arguments(); |
565 | |
566 | QString fileName; |
567 | QString outputFile; |
568 | bool verifyOnly = false; |
569 | |
570 | // By default ensure the output character width is less than 16-bits (pass 0 to disable) |
571 | int width = USHRT_MAX; |
572 | |
573 | int index = 1; |
574 | while (index < args.size()) { |
575 | const QString arg = args.at(i: index++); |
576 | const QString next = index < args.size() ? args.at(i: index) : QString(); |
577 | |
578 | if (arg == QLatin1String("-h" ) || arg == QLatin1String("--help" )) { |
579 | usage(/*showHelp*/ true); |
580 | return 0; |
581 | } else if (arg == QLatin1String("-v" ) || arg == QLatin1String("--verify-only" )) { |
582 | verifyOnly = true; |
583 | } else if (arg == QLatin1String("-o" )) { |
584 | if (next.isEmpty()) { |
585 | std::cerr << "qmlmin: argument to '-o' is missing" << std::endl; |
586 | return EXIT_FAILURE; |
587 | } else { |
588 | outputFile = next; |
589 | ++index; // consume the next argument |
590 | } |
591 | } else if (arg.startsWith(s: QLatin1String("-o" ))) { |
592 | outputFile = arg.mid(position: 2); |
593 | |
594 | if (outputFile.isEmpty()) { |
595 | std::cerr << "qmlmin: argument to '-o' is missing" << std::endl; |
596 | return EXIT_FAILURE; |
597 | } |
598 | } else if (arg == QLatin1String("-w" )) { |
599 | if (next.isEmpty()) { |
600 | std::cerr << "qmlmin: argument to '-w' is missing" << std::endl; |
601 | return EXIT_FAILURE; |
602 | } else { |
603 | bool ok; |
604 | width = next.toInt(ok: &ok); |
605 | |
606 | if (!ok) { |
607 | std::cerr << "qmlmin: argument to '-w' is invalid" << std::endl; |
608 | return EXIT_FAILURE; |
609 | } |
610 | |
611 | ++index; // consume the next argument |
612 | } |
613 | } else if (arg.startsWith(s: QLatin1String("-w" ))) { |
614 | bool ok; |
615 | width = arg.midRef(position: 2).toInt(ok: &ok); |
616 | |
617 | if (!ok) { |
618 | std::cerr << "qmlmin: argument to '-w' is invalid" << std::endl; |
619 | return EXIT_FAILURE; |
620 | } |
621 | } else { |
622 | const bool isInvalidOpt = arg.startsWith(c: QLatin1Char('-')); |
623 | if (! isInvalidOpt && fileName.isEmpty()) |
624 | fileName = arg; |
625 | else { |
626 | usage(/*show help*/ showHelp: isInvalidOpt); |
627 | if (isInvalidOpt) |
628 | std::cerr << "qmlmin: invalid option '" << qPrintable(arg) << '\'' << std::endl; |
629 | else |
630 | std::cerr << "qmlmin: too many input files specified" << std::endl; |
631 | return EXIT_FAILURE; |
632 | } |
633 | } |
634 | } |
635 | |
636 | if (fileName.isEmpty()) { |
637 | usage(); |
638 | return 0; |
639 | } |
640 | |
641 | std::cerr << "qmlmin: This tool is deprecated and will be removed in Qt 6. It is not needed anymore due to QtQml's built-in caching." << std::endl; |
642 | |
643 | QFile file(fileName); |
644 | if (! file.open(flags: QFile::ReadOnly)) { |
645 | std::cerr << "qmlmin: '" << qPrintable(fileName) << "' no such file or directory" << std::endl; |
646 | return EXIT_FAILURE; |
647 | } |
648 | |
649 | const QString code = QString::fromUtf8(str: file.readAll()); // QML files are UTF-8 encoded. |
650 | file.close(); |
651 | |
652 | QQmlJS::Minify minify(width); |
653 | if (! minify(fileName, code)) { |
654 | std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "' (not a valid QML/JS file)" << std::endl; |
655 | return EXIT_FAILURE; |
656 | } |
657 | |
658 | // |
659 | // verify the output |
660 | // |
661 | QQmlJS::Minify secondMinify(width); |
662 | if (! secondMinify(fileName, minify.minifiedCode()) || secondMinify.minifiedCode() != minify.minifiedCode()) { |
663 | std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << '\'' << std::endl; |
664 | return EXIT_FAILURE; |
665 | } |
666 | |
667 | QQmlJS::Tokenize originalTokens, minimizedTokens; |
668 | originalTokens(fileName, code); |
669 | minimizedTokens(fileName, minify.minifiedCode()); |
670 | |
671 | if (originalTokens.tokenStream().size() != minimizedTokens.tokenStream().size()) { |
672 | std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << '\'' << std::endl; |
673 | return EXIT_FAILURE; |
674 | } |
675 | |
676 | if (! verifyOnly) { |
677 | if (outputFile.isEmpty()) { |
678 | const QByteArray chars = minify.minifiedCode().toUtf8(); |
679 | std::cout << chars.constData(); |
680 | } else { |
681 | QFile file(outputFile); |
682 | if (! file.open(flags: QFile::WriteOnly)) { |
683 | std::cerr << "qmlmin: cannot minify '" << qPrintable(fileName) << "' (permission denied)" << std::endl; |
684 | return EXIT_FAILURE; |
685 | } |
686 | |
687 | file.write(data: minify.minifiedCode().toUtf8()); |
688 | file.close(); |
689 | } |
690 | } |
691 | |
692 | return 0; |
693 | } |
694 | |
695 | QT_END_NAMESPACE |
696 | |
697 | int main(int argc, char **argv) |
698 | { |
699 | return QT_PREPEND_NAMESPACE(runQmlmin(argc, argv)); |
700 | } |
701 | |