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
5#include <translator.h>
6#include "metastrings.h"
7#include "trparser.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 int startOffset = node->firstSourceLocation().begin();
104 if (!node->arguments) {
105 yyMsg(identLineNo)
106 << qPrintable(QStringLiteral("%1() requires at least one argument.\n")
107 .arg(name));
108 return;
109 }
110 if (auto expr = AST::cast<AST::TemplateLiteral *>(node->arguments->expression)) {
111 if (expr->next) {
112 yyMsg(identLineNo)
113 << qPrintable(QStringLiteral("%1() template strings with "
114 "arguments are not supported for translation.\n").arg(name));
115 return;
116 }
117 }
118
119 QString source;
120 if (!createString(node->arguments->expression, &source))
121 return;
122
123 QString comment;
124 bool plural = false;
125 if (AST::ArgumentList *commentNode = node->arguments->next) {
126 if (!createString(commentNode->expression, &comment)) {
127 comment.clear(); // clear possible invalid comments
128 }
129 if (commentNode->next)
130 plural = true;
131 }
132
133 if (!m_metaStrings.sourcetext().isEmpty())
134 yyMsg(identLineNo)
135 << qPrintable("//% cannot be used with %1(). Ignoring\n"_L1.arg(name));
136
137 if (!m_metaStrings.label().isEmpty())
138 yyMsg(identLineNo) << qPrintable(
139 "labels cannot be used with text-based translation. Ignoring\n"_L1);
140
141 int endOffset = node->lastSourceLocation().end();
142 TranslatorMessage msg(m_component, transcode(source), comment, QString(),
143 m_fileName, node->firstSourceLocation().startLine,
144 QStringList(), TranslatorMessage::Unfinished, plural);
145 msg.setExtraComment(transcode(m_metaStrings.extracomment().simplified()));
146 msg.setId(m_metaStrings.msgid());
147 msg.setExtras(m_metaStrings.extra());
148 msg.setStartOffset(startOffset);
149 msg.setEndOffset(endOffset);
150 m_translator->extend(msg, m_cd);
151 consumeComment();
152 break; }
153 case TrFunctionAliasManager::Function_qsTranslate:
154 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: {
155 int startOffset = node->firstSourceLocation().begin();
156 if (! (node->arguments && node->arguments->next)) {
157 yyMsg(identLineNo) << qPrintable(QStringLiteral("%1() requires at least two arguments.\n").arg(name));
158 return;
159 }
160
161 QString context;
162 if (!createString(node->arguments->expression, &context))
163 return;
164
165 AST::ArgumentList *sourceNode = node->arguments->next; // we know that it is a valid pointer.
166
167 QString source;
168 if (!createString(sourceNode->expression, &source))
169 return;
170
171 if (!m_metaStrings.sourcetext().isEmpty())
172 yyMsg(identLineNo) << qPrintable(QStringLiteral("//% cannot be used with %1(). Ignoring\n").arg(name));
173
174 if (!m_metaStrings.label().isEmpty())
175 yyMsg(identLineNo) << qPrintable(
176 "labels cannot be used with text-based translation. Ignoring\n"_L1);
177
178 QString comment;
179 bool plural = false;
180 if (AST::ArgumentList *commentNode = sourceNode->next) {
181 if (!createString(commentNode->expression, &comment)) {
182 comment.clear(); // clear possible invalid comments
183 }
184
185 if (commentNode->next)
186 plural = true;
187 }
188 int endOffset = node->lastSourceLocation().end();
189 TranslatorMessage msg(context, transcode(source), comment, QString(), m_fileName,
190 node->firstSourceLocation().startLine, QStringList(),
191 TranslatorMessage::Unfinished, plural);
192 msg.setExtraComment(transcode(m_metaStrings.extracomment().simplified()));
193 msg.setId(m_metaStrings.msgid());
194 msg.setExtras(m_metaStrings.extra());
195 msg.setStartOffset(startOffset);
196 msg.setEndOffset(endOffset);
197 m_translator->extend(msg, m_cd);
198 consumeComment();
199 break; }
200 case TrFunctionAliasManager::Function_qsTrId:
201 case TrFunctionAliasManager::Function_QT_TRID_NOOP: {
202 if (!node->arguments) {
203 yyMsg(identLineNo) << qPrintable(QStringLiteral("%1() requires at least one argument.\n").arg(name));
204 return;
205 }
206 int startOffset = node->firstSourceLocation().begin();
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 int endOffset = node->lastSourceLocation().end();
219 TranslatorMessage msg(QString(), transcode(m_metaStrings.sourcetext()), QString(),
220 QString(), m_fileName, node->firstSourceLocation().startLine,
221 QStringList(), TranslatorMessage::Unfinished, plural);
222 msg.setExtraComment(transcode(m_metaStrings.extracomment().simplified()));
223 msg.setId(id);
224 msg.setLabel(m_metaStrings.label());
225 msg.setExtras(m_metaStrings.extra());
226 msg.setStartOffset(startOffset);
227 msg.setEndOffset(endOffset);
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.resolveLabel(m_fileName, m_component))
359 yyMsg(loc.startLine) << m_metaStrings.popError().toStdString();
360
361 if (m_metaStrings.magicComment()) {
362 auto [context, comment] = *m_metaStrings.magicComment();
363 TranslatorMessage msg(transcode(context), QString(), transcode(comment), QString(),
364 m_fileName, loc.startLine, QStringList(), TranslatorMessage::Finished,
365 false);
366 msg.setExtraComment(transcode(m_metaStrings.extracomment().simplified()));
367 m_translator->append(msg);
368 m_translator->setExtras(m_metaStrings.extra());
369 m_metaStrings.clear();
370 }
371}
372
374{
375public:
376 HasDirectives(Lexer *lexer)
377 : lexer(lexer)
378 , directives(0)
379 {
380 }
381
382 bool operator()() const { return directives != 0; }
383 int end() const { return lastOffset; }
384
385 void pragmaLibrary() override { consumeDirective(); }
386 void importFile(const QString &, const QString &, int, int) override { consumeDirective(); }
387 void importModule(const QString &, const QString &, const QString &, int, int) override { consumeDirective(); }
388
389private:
390 void consumeDirective()
391 {
392 ++directives;
393 lastOffset = lexer->tokenOffset() + lexer->tokenLength();
394 }
395
396private:
397 Lexer *lexer;
398 int directives;
399 int lastOffset;
400};
401
407
408static bool load(Translator &translator, const QString &filename, ConversionData &cd, CodeType mode)
409{
410 cd.m_sourceFileName = filename;
411 QFile file(filename);
412 if (!file.open(QIODevice::ReadOnly)) {
413 cd.appendError(QStringLiteral("Cannot open %1: %2").arg(filename, file.errorString()));
414 return false;
415 }
416
417 QString code;
418 if (mode != QMLCode) {
419 code = QTextStream(&file).readAll();
420 } else {
421 QTextStream ts(&file);
422 code = ts.readAll();
423 }
424
425 Engine driver;
426 Parser parser(&driver);
427
428 Lexer lexer(&driver);
429 lexer.setCode(code, /*line = */ 1, mode == QMLCode);
430 driver.setLexer(&lexer);
431
432 bool rc;
433 if (mode == QMLCode)
434 rc = parser.parse();
435 else if (mode == JSCode)
436 rc = parser.parseProgram();
437 else
438 rc = parser.parseModule();
439
440 if (rc) {
441 FindTrCalls trCalls(&driver, cd);
442
443 //find all tr calls in the code
444 trCalls(&translator, filename, parser.rootNode());
445 } else {
446 QString error = createErrorString(filename, code, parser);
447 cd.appendError(error);
448 return false;
449 }
450 return true;
451}
452
453bool loadQml(Translator &translator, const QString &filename, ConversionData &cd)
454{
455 return load(translator, filename, cd, /*qmlMode=*/ QMLCode);
456}
457
458bool loadQScript(Translator &translator, const QString &filename, ConversionData &cd)
459{
460 return load(translator, filename, cd, /*qmlMode=*/ JSCode);
461}
462
463bool loadJSModule(Translator &translator, const QString &filename, ConversionData &cd)
464{
465 return load(translator, filename, cd, /*qmlMode=*/ MJSCode);
466}
467
468QT_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 setEndOffset(int endOffset)
void setExtras(const ExtraData &extras)
void setStartOffset(int startOffset)
void setExtras(const ExtraData &extras)
Definition translator.h:156
void append(const TranslatorMessage &msg)
void extend(const TranslatorMessage &msg, 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)
TrFunctionAliasManager trFunctionAliasManager
Definition trparser.cpp:153
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)