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