Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdeclarative.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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 "lupdate.h"
5
6#include <translator.h>
7
8#include <QtCore/QDebug>
9#include <QtCore/QFile>
10#include <QtCore/QString>
11#include <QtCore/QTextStream>
12
13#include <private/qqmljsengine_p.h>
14#include <private/qqmljsparser_p.h>
15#include <private/qqmljslexer_p.h>
16#include <private/qqmljsastvisitor_p.h>
17#include <private/qqmljsast_p.h>
18
19#include <QCoreApplication>
20#include <QFile>
21#include <QFileInfo>
22#include <QtDebug>
23#include <QStringList>
24
25#include <iostream>
26#include <cstdlib>
27#include <cctype>
28
29QT_BEGIN_NAMESPACE
30
31using namespace QQmlJS;
32
33using namespace Qt::StringLiterals;
34
35class FindTrCalls: protected AST::Visitor
36{
37public:
38 FindTrCalls(Engine *engine, ConversionData &cd)
39 : engine(engine)
40 , m_cd(cd)
41 {
42 }
43
44 void operator()(Translator *translator, const QString &fileName, AST::Node *node)
45 {
46 m_todo = engine->comments();
47 m_translator = translator;
48 m_fileName = fileName;
49 m_component = QFileInfo(fileName).completeBaseName();
50 accept(node);
51
52 // process the trailing comments
53 processComments(0, /*flush*/ true);
54 }
55
56protected:
57 using AST::Visitor::visit;
58 using AST::Visitor::endVisit;
59
60 void accept(AST::Node *node)
61 { AST::Node::accept(node, this); }
62
63 bool visit(AST::UiPragma *node) override
64 {
65 if (!node->name.isNull()) {
66 if (node->name == "Translator"_L1) {
67 m_component = node->values->value.toString();
68 }
69 }
70 return false;
71 }
72
73 void endVisit(AST::CallExpression *node) override
74 {
75 QString name;
76 AST::ExpressionNode *base = node->base;
77
78 while (base && base->kind == AST::Node::Kind_FieldMemberExpression) {
79 auto memberExpr = static_cast<AST::FieldMemberExpression *>(base);
80 name.prepend(memberExpr->name);
81 name.prepend(QLatin1Char('.'));
82 base = memberExpr->base;
83 }
84
85 if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(base)) {
86 processComments(idExpr->identifierToken.begin());
87
88 name = idExpr->name.toString() + name;
89 const int identLineNo = idExpr->identifierToken.startLine;
90 switch (trFunctionAliasManager.trFunctionByName(name)) {
91 case TrFunctionAliasManager::Function_qsTr:
92 case TrFunctionAliasManager::Function_QT_TR_NOOP: {
93 if (!node->arguments) {
94 yyMsg(identLineNo)
95 << qPrintable(QStringLiteral("%1() requires at least one argument.\n")
96 .arg(name));
97 return;
98 }
99
100 QString source;
101 if (!createString(node->arguments->expression, &source))
102 return;
103
104 QString comment;
105 bool plural = false;
106 if (AST::ArgumentList *commentNode = node->arguments->next) {
107 if (!createString(commentNode->expression, &comment)) {
108 comment.clear(); // clear possible invalid comments
109 }
110 if (commentNode->next)
111 plural = true;
112 }
113
114 if (!sourcetext.isEmpty())
115 yyMsg(identLineNo) << qPrintable(QStringLiteral("//% cannot be used with %1(). Ignoring\n").arg(name));
116
117 TranslatorMessage msg(m_component, ParserTool::transcode(source),
118 comment, QString(), m_fileName,
119 node->firstSourceLocation().startLine, QStringList(),
120 TranslatorMessage::Unfinished, plural);
121 msg.setExtraComment(ParserTool::transcode(extracomment.simplified()));
122 msg.setId(msgid);
123 msg.setExtras(extra);
124 m_translator->extend(msg, m_cd);
125 consumeComment();
126 break; }
127 case TrFunctionAliasManager::Function_qsTranslate:
128 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: {
129 if (! (node->arguments && node->arguments->next)) {
130 yyMsg(identLineNo) << qPrintable(QStringLiteral("%1() requires at least two arguments.\n").arg(name));
131 return;
132 }
133
134 QString context;
135 if (!createString(node->arguments->expression, &context))
136 return;
137
138 AST::ArgumentList *sourceNode = node->arguments->next; // we know that it is a valid pointer.
139
140 QString source;
141 if (!createString(sourceNode->expression, &source))
142 return;
143
144 if (!sourcetext.isEmpty())
145 yyMsg(identLineNo) << qPrintable(QStringLiteral("//% cannot be used with %1(). Ignoring\n").arg(name));
146
147 QString comment;
148 bool plural = false;
149 if (AST::ArgumentList *commentNode = sourceNode->next) {
150 if (!createString(commentNode->expression, &comment)) {
151 comment.clear(); // clear possible invalid comments
152 }
153
154 if (commentNode->next)
155 plural = true;
156 }
157
158 TranslatorMessage msg(context, ParserTool::transcode(source),
159 comment, QString(), m_fileName,
160 node->firstSourceLocation().startLine, QStringList(),
161 TranslatorMessage::Unfinished, plural);
162 msg.setExtraComment(ParserTool::transcode(extracomment.simplified()));
163 msg.setId(msgid);
164 msg.setExtras(extra);
165 m_translator->extend(msg, m_cd);
166 consumeComment();
167 break; }
168 case TrFunctionAliasManager::Function_qsTrId:
169 case TrFunctionAliasManager::Function_QT_TRID_NOOP: {
170 if (!node->arguments) {
171 yyMsg(identLineNo) << qPrintable(QStringLiteral("%1() requires at least one argument.\n").arg(name));
172 return;
173 }
174
175 QString id;
176 if (!createString(node->arguments->expression, &id))
177 return;
178
179 if (!msgid.isEmpty()) {
180 yyMsg(identLineNo) << qPrintable(QStringLiteral("//= cannot be used with %1(). Ignoring\n").arg(name));
181 return;
182 }
183
184 bool plural = node->arguments->next;
185
186 TranslatorMessage msg(QString(), ParserTool::transcode(sourcetext),
187 QString(), QString(), m_fileName,
188 node->firstSourceLocation().startLine, QStringList(),
189 TranslatorMessage::Unfinished, plural);
190 msg.setExtraComment(ParserTool::transcode(extracomment.simplified()));
191 msg.setId(id);
192 msg.setExtras(extra);
193 m_translator->extend(msg, m_cd);
194 consumeComment();
195 break; }
196 }
197 }
198 }
199
200 void postVisit(AST::Node *node) override;
201
202private:
203 std::ostream &yyMsg(int line)
204 {
205 return std::cerr << qPrintable(m_fileName) << ':' << line << ": ";
206 }
207
209 {
210 std::cerr << qPrintable(m_fileName) << ": "
211 << "Maximum statement or expression depth exceeded";
212 }
213
214
215 void processComments(quint32 offset, bool flush = false);
216 void processComment(const SourceLocation &loc);
217 void consumeComment();
218
219 bool createString(AST::ExpressionNode *ast, QString *out)
220 {
221 if (AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(ast)) {
222 out->append(literal->value);
223 return true;
224 } else if (AST::BinaryExpression *binop = AST::cast<AST::BinaryExpression *>(ast)) {
225 if (binop->op == QSOperator::Add && createString(binop->left, out)) {
226 if (createString(binop->right, out))
227 return true;
228 }
229 } else if (AST::TemplateLiteral *templit = AST::cast<AST::TemplateLiteral *>(ast)) {
230 out->append(templit->value);
231 return true;
232 }
233
234 return false;
235 }
236
237 Engine *engine;
238 Translator *m_translator;
239 ConversionData &m_cd;
240 QString m_fileName;
241 QString m_component;
242
243 // comments
244 QString extracomment;
245 QString msgid;
247 QString sourcetext;
248 QString trcontext;
249 QList<SourceLocation> m_todo;
250};
251
252QString createErrorString(const QString &filename, const QString &code, Parser &parser)
253{
254 // print out error
255 QStringList lines = code.split(QLatin1Char('\n'));
256 lines.append(QLatin1String("\n")); // sentinel.
257 QString errorString;
258
259 const auto messages = parser.diagnosticMessages();
260 for (const DiagnosticMessage &m : messages) {
261
262 if (m.isWarning())
263 continue;
264
265 const int line = m.loc.startLine;
266 const int column = m.loc.startColumn;
267 QString error = filename + QLatin1Char(':')
268 + QString::number(line) + QLatin1Char(':') + QString::number(column)
269 + QLatin1String(": error: ") + m.message + QLatin1Char('\n');
270
271 const QString textLine = lines.at(line > 0 ? line - 1 : 0);
272 error += textLine + QLatin1Char('\n');
273 for (int i = 0, end = qMin(column > 0 ? column - 1 : 0, textLine.size()); i < end; ++i) {
274 const QChar ch = textLine.at(i);
275 if (ch.isSpace())
276 error += ch;
277 else
278 error += QLatin1Char(' ');
279 }
280 error += QLatin1String("^\n");
281 errorString += error;
282 }
283 return errorString;
284}
285
286void FindTrCalls::postVisit(AST::Node *node)
287{
288 if (node->statementCast() != 0 || node->uiObjectMemberCast()) {
289 processComments(node->lastSourceLocation().end());
290
291 if (!sourcetext.isEmpty() || !extracomment.isEmpty() || !msgid.isEmpty() || !extra.isEmpty()) {
292 yyMsg(node->lastSourceLocation().startLine) << "Discarding unconsumed meta data\n";
293 consumeComment();
294 }
295 }
296}
297
298void FindTrCalls::processComments(quint32 offset, bool flush)
299{
300 for (; !m_todo.isEmpty(); m_todo.removeFirst()) {
301 SourceLocation loc = m_todo.first();
302 if (! flush && (loc.begin() >= offset))
303 break;
304
305 processComment(loc);
306 }
307}
308
309void FindTrCalls::consumeComment()
310{
311 // keep the current `trcontext'
312 extracomment.clear();
313 msgid.clear();
314 extra.clear();
315 sourcetext.clear();
316}
317
318void FindTrCalls::processComment(const SourceLocation &loc)
319{
320 if (!loc.length)
321 return;
322
323 const QStringView commentStr = engine->midRef(loc.begin(), loc.length);
324 const QChar *chars = commentStr.constData();
325 const int length = commentStr.size();
326
327 // Try to match the logic of the C++ parser.
328 if (*chars == QLatin1Char(':') && chars[1].isSpace()) {
329 if (!extracomment.isEmpty())
330 extracomment += QLatin1Char(' ');
331 extracomment += QString(chars+2, length-2);
332 } else if (*chars == QLatin1Char('=') && chars[1].isSpace()) {
333 msgid = QString(chars+2, length-2).simplified();
334 } else if (*chars == QLatin1Char('~') && chars[1].isSpace()) {
335 QString text = QString(chars+2, length-2).trimmed();
336 int k = text.indexOf(QLatin1Char(' '));
337 if (k > -1) {
338 QString commentvalue = text.mid(k + 1).trimmed();
339 if (commentvalue.startsWith(QLatin1Char('"')) && commentvalue.endsWith(QLatin1Char('"'))
340 && commentvalue.size() != 1) {
341 commentvalue = commentvalue.sliced(1, commentvalue.size() - 2);
342 }
343 extra.insert(text.left(k), commentvalue);
344 }
345 } else if (*chars == QLatin1Char('%') && chars[1].isSpace()) {
346 sourcetext.reserve(sourcetext.size() + length-2);
347 ushort *ptr = (ushort *)sourcetext.data() + sourcetext.size();
348 int p = 2, c;
349 forever {
350 if (p >= length)
351 break;
352 c = chars[p++].unicode();
353 if (std::isspace(c))
354 continue;
355 if (c != '"') {
356 yyMsg(loc.startLine) << "Unexpected character in meta string\n";
357 break;
358 }
359 forever {
360 if (p >= length) {
361 whoops:
362 yyMsg(loc.startLine) << "Unterminated meta string\n";
363 break;
364 }
365 c = chars[p++].unicode();
366 if (c == '"')
367 break;
368 if (c == '\\') {
369 if (p >= length)
370 goto whoops;
371 c = chars[p++].unicode();
372 if (c == '\r' || c == '\n')
373 goto whoops;
374 *ptr++ = '\\';
375 }
376 *ptr++ = c;
377 }
378 }
379 sourcetext.resize(ptr - (ushort *)sourcetext.data());
380 } else {
381 int idx = 0;
382 ushort c;
383 while ((c = chars[idx].unicode()) == ' ' || c == '\t' || c == '\r' || c == '\n')
384 ++idx;
385 }
386}
387
389{
390public:
391 HasDirectives(Lexer *lexer)
392 : lexer(lexer)
393 , directives(0)
394 {
395 }
396
397 bool operator()() const { return directives != 0; }
398 int end() const { return lastOffset; }
399
400 void pragmaLibrary() override { consumeDirective(); }
401 void importFile(const QString &, const QString &, int, int) override { consumeDirective(); }
402 void importModule(const QString &, const QString &, const QString &, int, int) override { consumeDirective(); }
403
404private:
405 void consumeDirective()
406 {
407 ++directives;
408 lastOffset = lexer->tokenOffset() + lexer->tokenLength();
409 }
410
411private:
412 Lexer *lexer;
413 int directives;
414 int lastOffset;
415};
416
417static bool load(Translator &translator, const QString &filename, ConversionData &cd, bool qmlMode)
418{
419 cd.m_sourceFileName = filename;
420 QFile file(filename);
421 if (!file.open(QIODevice::ReadOnly)) {
422 cd.appendError(QStringLiteral("Cannot open %1: %2").arg(filename, file.errorString()));
423 return false;
424 }
425
426 QString code;
427 if (!qmlMode) {
428 code = QTextStream(&file).readAll();
429 } else {
430 QTextStream ts(&file);
431 code = ts.readAll();
432 }
433
434 Engine driver;
435 Parser parser(&driver);
436
437 Lexer lexer(&driver);
438 lexer.setCode(code, /*line = */ 1, qmlMode);
439 driver.setLexer(&lexer);
440
441 if (qmlMode ? parser.parse() : parser.parseProgram()) {
442 FindTrCalls trCalls(&driver, cd);
443
444 //find all tr calls in the code
445 trCalls(&translator, filename, parser.rootNode());
446 } else {
447 QString error = createErrorString(filename, code, parser);
448 cd.appendError(error);
449 return false;
450 }
451 return true;
452}
453
454bool loadQml(Translator &translator, const QString &filename, ConversionData &cd)
455{
456 return load(translator, filename, cd, /*qmlMode=*/ true);
457}
458
459bool loadQScript(Translator &translator, const QString &filename, ConversionData &cd)
460{
461 return load(translator, filename, cd, /*qmlMode=*/ false);
462}
463
464QT_END_NAMESPACE
FindTrCalls(Engine *engine, ConversionData &cd)
void endVisit(AST::CallExpression *node) override
void operator()(Translator *translator, const QString &fileName, AST::Node *node)
void throwRecursionDepthError() final
void accept(AST::Node *node)
void postVisit(AST::Node *node) override
bool visit(AST::UiPragma *node) override
void importFile(const QString &, const QString &, int, int) override
void pragmaLibrary() override
int end() const
HasDirectives(Lexer *lexer)
void importModule(const QString &, const QString &, const QString &, int, int) override
bool operator()() const
QHash< QString, QString > ExtraData
void setExtras(const ExtraData &extras)
void extend(const TranslatorMessage &msg, ConversionData &cd)
bool loadQml(Translator &translator, const QString &filename, ConversionData &cd)
bool loadQScript(Translator &translator, const QString &filename, ConversionData &cd)
static bool load(Translator &translator, const QString &filename, ConversionData &cd, bool qmlMode)
QString createErrorString(const QString &filename, const QString &code, Parser &parser)