| 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 |  |