1// Copyright (C) 2019 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 "clangtoolastreader.h"
5#include "filesignificancecheck.h"
6#include "translator.h"
7
8#include <QLibraryInfo>
9
10QT_BEGIN_NAMESPACE
11
12namespace LupdatePrivate
13{
14 void exploreChildrenForFirstStringLiteral(clang::Stmt* stmt, QString &context)
15 {
16 // only exploring the children until the context has been found.
17 if (!stmt || !context.isEmpty())
18 return;
19
20 for (auto it = stmt->child_begin() ; it !=stmt->child_end() ; it++) {
21 if (!context.isEmpty())
22 break;
23 clang::Stmt *child = *it;
24 clang::StringLiteral *stringLit = llvm::dyn_cast_or_null<clang::StringLiteral>(Val: child);
25 if (stringLit) {
26 context = toQt(str: stringLit->getString());
27 return;
28 }
29 exploreChildrenForFirstStringLiteral(stmt: child, context);
30 }
31 return;
32 }
33
34 // Checks if the tr method is supported by the CXXRecordDecl
35 // Either because Q_OBJECT or Q_DECLARE_FUNCTIONS(MyContext) is declared with this CXXRecordDecl
36 // In case of Q_DECLARE_FUNCTIONS the context is read in the tr Method children with function exploreChildrenForFirstStringLiteral
37 // Q_DECLARE_FUNCTIONS trace in the AST is:
38 // - a public AccessSpecDecl pointing to src/corelib/kernel/qcoreapplication.h
39 // - a CXXMethodDecl called tr with a children that is a StringLiteral. This is the context
40 // Q_OBJECT trace in the AST is:
41 // - a public AccessSpecDecl pointing to src/corelib/kernel/qtmetamacros.h
42 // - a CXXMethodDecl called tr WITHOUT a StringLiteral among its children.
43 bool isQObjectOrQDeclareTrFunctionMacroDeclared(clang::CXXRecordDecl *recordDecl, QString &context, const clang::SourceManager &sm)
44 {
45 if (!recordDecl)
46 return false;
47
48 bool tr_method_present = false;
49 bool access_for_qobject = false;
50 bool access_for_qdeclaretrfunction = false;
51
52 for (auto decl : recordDecl->decls()) {
53 clang::AccessSpecDecl *accessSpec = llvm::dyn_cast<clang::AccessSpecDecl>(Val: decl);
54 clang::CXXMethodDecl *method = llvm::dyn_cast<clang::CXXMethodDecl>(Val: decl);
55
56 if (!accessSpec && !method)
57 continue;
58 if (method) {
59 // Look for method with name 'tr'
60 std::string name = method->getNameAsString();
61 if (name == "tr") {
62 tr_method_present = true;
63 // if nothing is found and the context remains empty, it's ok, it's probably a Q_OBJECT.
64 exploreChildrenForFirstStringLiteral(stmt: method->getBody(), context);
65 }
66 } else if (accessSpec) {
67 if (!accessSpec->getBeginLoc().isValid())
68 continue;
69 QString location = QString::fromStdString(
70 s: sm.getSpellingLoc(Loc: accessSpec->getBeginLoc()).printToString(SM: sm));
71 qsizetype indexLast = location.lastIndexOf(s: QLatin1String(":"));
72 qsizetype indexBeforeLast = location.lastIndexOf(s: QLatin1String(":"), from: indexLast-1);
73 location.truncate(pos: indexBeforeLast);
74 const QString qtInstallDirPath = QLibraryInfo::path(p: QLibraryInfo::PrefixPath);
75 const QString accessForQDeclareTrFunctions = QStringLiteral("qcoreapplication.h");
76 const QString accessForQObject = QStringLiteral("qtmetamacros.h");
77 // Qt::CaseInsensitive because of potential discrepancy in Windows with D:/ and d:/
78 if (location.startsWith(s: qtInstallDirPath, cs: Qt::CaseInsensitive)) {
79 if (location.endsWith(s: accessForQDeclareTrFunctions))
80 access_for_qdeclaretrfunction = true;
81 if (location.endsWith(s: accessForQObject))
82 access_for_qobject = true;
83 }
84 }
85 }
86
87 bool access_to_qtbase = false;
88 // if the context is still empty then it cannot be a Q_DECLARE_TR_FUNCTION.
89 if (context.isEmpty())
90 access_to_qtbase = access_for_qobject;
91 else
92 access_to_qtbase = access_for_qdeclaretrfunction;
93
94 return tr_method_present && access_to_qtbase;
95 }
96
97 QString exploreBases(clang::CXXRecordDecl *recordDecl, const clang::SourceManager &sm);
98 QString lookForContext(clang::CXXRecordDecl *recordDecl, const clang::SourceManager &sm)
99 {
100 QString context;
101 if (isQObjectOrQDeclareTrFunctionMacroDeclared(recordDecl, context, sm)) {
102 return context.isEmpty() ? QString::fromStdString(s: recordDecl->getQualifiedNameAsString()) : context;
103 } else {
104 // explore the bases of this CXXRecordDecl
105 // the base class AA takes precedent over B (reproducing tr context behavior)
106 /*
107 class AA {Q_OBJECT};
108 class A : public AA {};
109 class B {
110 Q_OBJECT
111 class C : public A
112 {
113 QString c_tr = tr("context is AA");
114 const char * c_noop = QT_TR_NOOP("context should be AA");
115 }
116 };
117 */
118 // For recordDecl corresponding to class C, the following gives access to class A
119 return exploreBases(recordDecl, sm);
120 }
121 }
122
123 // Gives access to the class or struct the CXXRecordDecl is inheriting from
124 QString exploreBases(clang::CXXRecordDecl *recordDecl, const clang::SourceManager &sm)
125 {
126 QString context;
127 for (auto base : recordDecl->bases()) {
128 const clang::Type *type = base.getType().getTypePtrOrNull();
129 if (!type) continue;
130 clang::CXXRecordDecl *baseDecl = type->getAsCXXRecordDecl();
131 if (!baseDecl)
132 continue;
133 context = lookForContext(recordDecl: baseDecl, sm);
134 if (!context.isEmpty())
135 return context;
136 }
137 return context;
138 }
139
140 // QT_TR_NOOP location is within the the NamedDecl range
141 // Look for the RecordDecl (class or struct) the NamedDecl belongs to
142 // and the related classes until Q_OBJECT macro declaration or Q_DECLARE_TR_FUNCTIONS is found.
143 // The first class where Q_OBJECT or Q_DECLARE_TR_FUNCTIONS is declared is the context.
144 // The goal is to reproduce the behavior exibited by the new parser for tr function.
145 // tr function and QT_TR_NOOP, when next to each other in code, should always have the same context!
146 //
147 // The old parser does not do this.
148 // If a Q_OBJECT macro cannot be found in the first class
149 // a warning is emitted and the class is used as context regardless.
150 // This is the behavior for tr function and QT_TR_NOOP
151 // This is not correct.
152 QString contextForNoopMacro(clang::NamedDecl *namedDecl, const clang::SourceManager &sm)
153 {
154 QString context;
155 clang::DeclContext *decl = namedDecl->getDeclContext();
156 if (!decl)
157 return context;
158 while (decl) {
159 qCDebug(lcClang) << "--------------------- decl kind name: " << decl->getDeclKindName();
160 if (clang::isa<clang::CXXRecordDecl>(Val: decl)) {
161 clang::CXXRecordDecl *recordDecl = llvm::dyn_cast<clang::CXXRecordDecl>(Val: decl);
162
163 context = lookForContext(recordDecl, sm);
164
165 if (!context.isEmpty())
166 return context;
167 }
168 decl = decl->getParent(); // Brings to the class or struct decl is nested in, if it exists.
169 }
170
171 // If no context has been found: do not emit a warning here.
172 // because more than one NamedDecl can include the QT_TR_NOOP macro location
173 // in the following, class A and class B and c_noop will.
174 /*
175 class A {
176 class B
177 {
178 Q_OBJECT
179 const char * c_noop = QT_TR_NOOP("context is B");
180 }
181 };
182 */
183 // calling contextForNoopMacro on NamedDecl corresponding to class A
184 // no context will be found, but it's ok because the context will be found
185 // when the function is called on c_noop.
186 return context;
187 }
188
189
190 QString contextForFunctionDecl(clang::FunctionDecl *func, const std::string &funcName)
191 {
192 std::string context;
193#if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0))
194 {
195 llvm::raw_string_ostream tmp(context);
196 func->printQualifiedName(OS&: tmp);
197 }
198#else
199 context = func->getQualifiedNameAsString();
200#endif
201 return QString::fromStdString(s: context.substr(pos: 0, n: context.find(str: "::" + funcName, pos: 0)));
202 }
203
204 static bool capture(const QRegularExpression &exp, const QString &line, QString *i, QString *c)
205 {
206 i->clear(), c->clear();
207 auto result = exp.match(subject: line);
208 if (!result.hasMatch())
209 return false;
210
211 *i = result.captured(name: QLatin1String("identifier"));
212 *c = result.captured(QStringLiteral("comment")).trimmed();
213
214 if (*i == QLatin1String("%"))
215 *c = LupdatePrivate::cleanQuote(s: c->toStdString(), quote: QuoteCompulsary::Left);
216
217 return !c->isEmpty();
218 }
219
220 bool hasQuote(llvm::StringRef source)
221 {
222 return source.contains(Other: "\"");
223 }
224
225 bool trFunctionPresent(llvm::StringRef text)
226 {
227 if (text.contains(Other: llvm::StringRef("qtTrId(")))
228 return true;
229 if (text.contains(Other: llvm::StringRef("tr(")))
230 return true;
231 if (text.contains(Other: llvm::StringRef("trUtf8(")))
232 return true;
233 if (text.contains(Other: llvm::StringRef("translate(")))
234 return true;
235 if (text.contains(Other: llvm::StringRef("Q_DECLARE_TR_FUNCTIONS(")))
236 return true;
237 if (text.contains(Other: llvm::StringRef("QT_TR_N_NOOP(")))
238 return true;
239 if (text.contains(Other: llvm::StringRef("QT_TRID_N_NOOP(")))
240 return true;
241 if (text.contains(Other: llvm::StringRef("QT_TRANSLATE_N_NOOP(")))
242 return true;
243 if (text.contains(Other: llvm::StringRef("QT_TRANSLATE_N_NOOP3(")))
244 return true;
245 if (text.contains(Other: llvm::StringRef("QT_TR_NOOP(")))
246 return true;
247 if (text.contains(Other: llvm::StringRef("QT_TRID_NOOP(")))
248 return true;
249 if (text.contains(Other: llvm::StringRef("QT_TRANSLATE_NOOP(")))
250 return true;
251 if (text.contains(Other: llvm::StringRef("QT_TRANSLATE_NOOP3(")))
252 return true;
253 if (text.contains(Other: llvm::StringRef("QT_TR_NOOP_UTF8(")))
254 return true;
255 if (text.contains(Other: llvm::StringRef("QT_TRANSLATE_NOOP_UTF8(")))
256 return true;
257 if (text.contains(Other: llvm::StringRef("QT_TRANSLATE_NOOP3_UTF8(")))
258 return true;
259 return false;
260 }
261
262 bool isPointWithin(const clang::SourceRange &sourceRange, const clang::SourceLocation &point,
263 const clang::SourceManager &sm)
264 {
265 clang::SourceLocation start = sourceRange.getBegin();
266 clang::SourceLocation end = sourceRange.getEnd();
267 return point == start || point == end || (sm.isBeforeInTranslationUnit(LHS: start, RHS: point)
268 && sm.isBeforeInTranslationUnit(LHS: point, RHS: end));
269 }
270
271 class BeforeThanCompare
272 {
273 const clang::SourceManager &SM;
274
275 public:
276 explicit BeforeThanCompare(const clang::SourceManager &SM) : SM(SM) { }
277
278 bool operator()(const clang::RawComment &LHS, const clang::RawComment &RHS)
279 {
280 return SM.isBeforeInTranslationUnit(LHS: LHS.getBeginLoc(), RHS: RHS.getBeginLoc());
281 }
282
283 bool operator()(const clang::RawComment *LHS, const clang::RawComment *RHS)
284 {
285 return operator()(LHS: *LHS, RHS: *RHS);
286 }
287 };
288}
289
290/*
291 The visit call expression function is called automatically after the
292 visitor TraverseAST function is called. This is the function where the
293 "tr", "trUtf8", "qtIdTr", "translate" functions are picked up in the AST.
294 Previously mentioned functions are always part of a CallExpression.
295*/
296bool LupdateVisitor::VisitCallExpr(clang::CallExpr *callExpression)
297{
298 const auto fullLocation = m_context->getFullLoc(Loc: callExpression->getBeginLoc());
299 if (fullLocation.isInvalid())
300 return true;
301 clang::FunctionDecl *func = callExpression->getDirectCallee();
302 if (!func)
303 return true;
304 clang::QualType q = callExpression->getType();
305 if (!q.getTypePtrOrNull())
306 return true;
307
308 struct {
309 unsigned Line;
310 std::string Filename;
311 } info;
312
313 const auto funcName = QString::fromStdString(s: func->getNameInfo().getAsString());
314
315 // Only continue if the function a translation function (TODO: deal with alias function...)
316 switch (trFunctionAliasManager.trFunctionByName(trFunctionName: funcName)) {
317 case TrFunctionAliasManager::Function_tr:
318 case TrFunctionAliasManager::Function_trUtf8:
319 case TrFunctionAliasManager::Function_translate:
320 case TrFunctionAliasManager::Function_qtTrId:{
321
322 const auto &sm = m_context->getSourceManager();
323 const auto fileLoc = sm.getFileLoc(Loc: callExpression->getBeginLoc());
324 if (fileLoc.isInvalid() || !fileLoc.isFileID())
325 return true;
326 // not using line directive (# line)
327 auto presumedLoc = sm.getPresumedLoc(Loc: fileLoc, UseLineDirectives: false);
328 if (presumedLoc.isInvalid())
329 return true;
330 info = { .Line: presumedLoc.getLine(), .Filename: presumedLoc.getFilename() };
331 } break;
332 default:
333 return true;
334 }
335
336 // Checking that the CallExpression is from the input file we're interested in
337 if (!LupdatePrivate::isFileSignificant(filePath: info.Filename))
338 return true;
339
340 qCDebug(lcClang) << "************************** VisitCallExpr ****************";
341 // Retrieving the information needed to fill the lupdate translator.
342 // Function independent retrieve
343 TranslationRelatedStore store;
344 store.callType = QStringLiteral("ASTRead_CallExpr");
345 store.funcName = funcName;
346 store.lupdateLocationFile = QString::fromStdString(s: info.Filename);
347 store.lupdateLocationLine = info.Line;
348 store.contextRetrieved = LupdatePrivate::contextForFunctionDecl(func, funcName: funcName.toStdString());
349
350 qCDebug(lcClang) << "CallType : ASTRead_CallExpr";
351 qCDebug(lcClang) << "Function name : " << store.funcName;
352 qCDebug(lcClang) << "File location : " << store.lupdateLocationFile;
353 qCDebug(lcClang) << "Line : " << store.lupdateLocationLine;
354 qCDebug(lcClang) << "Context retrieved : " << store.contextRetrieved;
355
356 // Here we gonna need to retrieve the comments around the function call
357 // //: //* //~ Things like that
358 const std::vector<QString> rawComments = rawCommentsForCallExpr(callExpr: callExpression);
359 for (const auto &rawComment : rawComments) {
360 setInfoFromRawComment(commentString: rawComment, store: &store);
361 qCDebug(lcClang) << "Raw comments :" << rawComment;
362 }
363
364 clang::LangOptions langOpts;
365 langOpts.CPlusPlus = true;
366 clang::PrintingPolicy policy(langOpts);
367 std::vector<std::string> arguments(callExpression->getNumArgs(), "");
368 for (unsigned int i = 0; i < callExpression->getNumArgs(); i++) {
369 auto arg = callExpression->getArg(Arg: i);
370 llvm::raw_string_ostream temp(arguments[i]);
371 arg->printPretty(OS&: temp, Helper: nullptr, Policy: policy);
372 }
373
374 // Function dependent retrieve!
375 switch (trFunctionAliasManager.trFunctionByName(trFunctionName: funcName)) {
376 case TrFunctionAliasManager::Function_tr:
377 case TrFunctionAliasManager::Function_trUtf8:
378 if (arguments.size() != 3 || !LupdatePrivate::hasQuote(source: arguments[0]))
379 return true;
380 store.lupdateSource = LupdatePrivate::cleanQuote(token: arguments[0]);
381 store.lupdateComment = LupdatePrivate::cleanQuote(token: arguments[1]);
382 store.lupdatePlural = QString::fromStdString(s: arguments[2]);
383 qCDebug(lcClang) << "Source : " << store.lupdateSource;
384 qCDebug(lcClang) << "Comment : " << store.lupdateComment;
385 qCDebug(lcClang) << "Plural : " << store.lupdatePlural;
386 break;
387 case TrFunctionAliasManager::Function_translate:
388 if (arguments.size() != 4 || !LupdatePrivate::hasQuote(source: arguments[0])
389 || !LupdatePrivate::hasQuote(source: arguments[1])) {
390 return true;
391 }
392 store.contextArg = LupdatePrivate::cleanQuote(token: arguments[0]);
393 store.lupdateSource = LupdatePrivate::cleanQuote(token: arguments[1]);
394 store.lupdateComment = LupdatePrivate::cleanQuote(token: arguments[2]);
395 store.lupdatePlural = QString::fromStdString(s: arguments[3]);
396 qCDebug(lcClang) << "Context Arg : " << store.contextArg;
397 qCDebug(lcClang) << "Source : " << store.lupdateSource;
398 qCDebug(lcClang) << "Comment : " << store.lupdateComment;
399 qCDebug(lcClang) << "Plural : " << store.lupdatePlural;
400 break;
401 case TrFunctionAliasManager::Function_qtTrId:
402 if (arguments.size() != 2 || !LupdatePrivate::hasQuote(source: arguments[0]))
403 return true;
404 store.lupdateId = LupdatePrivate::cleanQuote(token: arguments[0]);
405 store.lupdatePlural = QString::fromStdString(s: arguments[1]);
406 qCDebug(lcClang) << "ID : " << store.lupdateId;
407 qCDebug(lcClang) << "Plural : " << store.lupdatePlural;
408 break;
409 }
410 // locationCol needs to be set for the store to be considered valid (but really only needed for PP calls, to reconstruct location)
411 store.locationCol = 0;
412 m_trCalls.emplace_back(args: std::move(store));
413 return true;
414}
415
416void LupdateVisitor::processIsolatedComments()
417{
418 auto &sourceMgr = m_context->getSourceManager();
419 processIsolatedComments(file: sourceMgr.getMainFileID()) ;
420}
421
422/*
423 Retrieve the comments not associated with tr calls.
424*/
425void LupdateVisitor::processIsolatedComments(const clang::FileID file)
426{
427 qCDebug(lcClang) << "==== processIsolatedComments ====";
428 auto &sourceMgr = m_context->getSourceManager();
429
430#if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0))
431 const auto commentsInThisFile = m_context->Comments.getCommentsInFile(File: file);
432 if (!commentsInThisFile)
433 return;
434
435 std::vector<clang::RawComment *> tmp;
436 for (const auto &commentInFile : *commentsInThisFile)
437 tmp.emplace_back(args: commentInFile.second);
438 clang::ArrayRef<clang::RawComment *> rawComments = tmp;
439#else
440 Q_UNUSED(file);
441 clang::ArrayRef<clang::RawComment *> rawComments = m_context->getRawCommentList().getComments();
442#endif
443
444 // If there are no comments anywhere, we won't find anything.
445 if (rawComments.empty())
446 return;
447
448 // Searching for the comments of the form:
449 // /* TRANSLATOR CONTEXT
450 // whatever */
451 // They are not associated to any tr calls
452 // Each one needs its own entry in the m_stores->AST translation store
453 for (const auto &rawComment : rawComments) {
454 if (!LupdatePrivate::isFileSignificant(filePath: sourceMgr.getFilename(SpellingLoc: rawComment->getBeginLoc()).str()))
455 continue;
456 // Comments not separated by an empty line will be part of the same Raw comments
457 // Each one needs to be saved with its line number.
458 // The store is used here only to pass this information.
459 TranslationRelatedStore store;
460 store.lupdateLocationLine = sourceMgr.getPresumedLoc(Loc: rawComment->getBeginLoc(), UseLineDirectives: false).getLine();
461 store.lupdateLocationFile = QString::fromStdString(
462 s: sourceMgr.getPresumedLoc(Loc: rawComment->getBeginLoc(), UseLineDirectives: false).getFilename());
463 QString comment = toQt(str: rawComment->getRawText(SourceMgr: sourceMgr));
464 qCDebug(lcClang) << " raw Comment : \n" << comment;
465 setInfoFromRawComment(commentString: comment, store: &store);
466 }
467}
468
469/*
470 Retrieve the comments associated with the CallExpression.
471*/
472std::vector<QString> LupdateVisitor::rawCommentsForCallExpr(const clang::CallExpr *callExpr) const
473{
474 if (!m_context)
475 return {};
476 return rawCommentsFromSourceLocation(sourceLocation: m_context->getFullLoc(Loc: callExpr->getBeginLoc()));
477}
478
479std::vector<QString> LupdateVisitor::rawCommentsFromSourceLocation(
480 clang::SourceLocation sourceLocation) const
481{
482 if (!m_context)
483 return {};
484 if (sourceLocation.isInvalid() || !sourceLocation.isFileID()) {
485 qCDebug(lcClang) << "The declaration does not map directly to a location in a file,"
486 " early return.";
487 return {};
488 }
489 auto &sourceMgr = m_context->getSourceManager();
490
491#if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0))
492 const clang::FileID file = sourceMgr.getDecomposedLoc(Loc: sourceLocation).first;
493 const auto commentsInThisFile = m_context->Comments.getCommentsInFile(File: file);
494 if (!commentsInThisFile)
495 return {};
496
497 std::vector<clang::RawComment *> tmp;
498 for (const auto &commentInFile : *commentsInThisFile)
499 tmp.emplace_back(args: commentInFile.second);
500 clang::ArrayRef<clang::RawComment *> rawComments = tmp;
501#else
502 clang::ArrayRef<clang::RawComment *> rawComments = m_context->getRawCommentList().getComments();
503#endif
504
505 // If there are no comments anywhere, we won't find anything.
506 if (rawComments.empty())
507 return {};
508
509 // Create a dummy raw comment with the source location of the declaration.
510 clang::RawComment commentAtDeclarationLocation(sourceMgr,
511 clang::SourceRange(sourceLocation), m_context->getLangOpts().CommentOpts, false);
512
513 // Create a functor object to compare the source location of the comment and the declaration.
514 const LupdatePrivate::BeforeThanCompare compareSourceLocation(sourceMgr);
515 // Find the comment that occurs just after or within this declaration. Possible findings:
516 // QObject::tr(/* comment 1 */ "test"); //: comment 2 -> finds "//: comment 1"
517 // QObject::tr("test"); //: comment 1 -> finds "//: comment 1"
518 // QObject::tr("test");
519 // //: comment 1 -> finds "//: comment 1"
520 // /*: comment 1 */ QObject::tr("test"); -> finds no trailing comment
521 auto comment = std::lower_bound(first: rawComments.begin(), last: rawComments.end(),
522 val: &commentAtDeclarationLocation, comp: compareSourceLocation);
523
524 // We did not find any comment before the declaration.
525 if (comment == rawComments.begin())
526 return {};
527
528 // Decompose the location for the declaration and find the beginning of the file buffer.
529 std::pair<clang::FileID, unsigned> declLocDecomp = sourceMgr.getDecomposedLoc(Loc: sourceLocation);
530
531 // Get the text buffer from the beginning of the file up through the declaration's begin.
532 bool invalid = false;
533 const char *buffer = sourceMgr.getBufferData(FID: declLocDecomp.first, Invalid: &invalid).data();
534 if (invalid) {
535 qCDebug(lcClang).nospace() << "An error occurred fetching the source buffer of file: "
536 << toQt(str: sourceMgr.getFilename(SpellingLoc: sourceLocation));
537 return {};
538 }
539
540 std::vector<QString> retrievedRawComments;
541 auto lastDecompLoc = declLocDecomp.second;
542 const auto declLineNum = sourceMgr.getLineNumber(FID: declLocDecomp.first, FilePos: declLocDecomp.second);
543 do {
544 std::advance(i&: comment, n: -1);
545
546 // Decompose the end of the comment.
547 std::pair<clang::FileID, unsigned> commentEndDecomp
548 = sourceMgr.getDecomposedLoc(Loc: (*comment)->getSourceRange().getEnd());
549
550 // If the comment and the declaration aren't in the same file, then they aren't related.
551 if (declLocDecomp.first != commentEndDecomp.first) {
552 qCDebug(lcClang) << "Comment and the declaration aren't in the same file. Comment '"
553 << toQt(str: (*comment)->getRawText(SourceMgr: sourceMgr)) << "' is ignored, return.";
554 return retrievedRawComments;
555 }
556
557 // Current lupdate ignores comments on the same line before the declaration.
558 // void Class42::hello(int something /*= 17 */, QString str = Class42::tr("eyo"))
559 bool sameLineComment = false;
560 if (declLineNum == sourceMgr.getLineNumber(FID: commentEndDecomp.first, FilePos: commentEndDecomp.second))
561 sameLineComment = true;
562
563 // Extract text between the comment and declaration.
564 llvm::StringRef text(buffer + commentEndDecomp.second,
565 lastDecompLoc - commentEndDecomp.second);
566
567 // There should be no other declarations or preprocessor directives between
568 // comment and declaration.
569 if (text.find_first_of(Chars: ";}#@") != llvm::StringRef::npos) {
570 qCDebug(lcClang) << "Found another declaration or preprocessor directive between"
571 " comment and declaration, break.";
572 break;
573 }
574 if (sameLineComment && text.find_first_of(Chars: ",") != llvm::StringRef::npos) {
575 qCDebug(lcClang) << "Comment ends on same line as the declaration and is separated "
576 "from the tr call by a ','. Comment '"
577 << toQt(str: (*comment)->getRawText(SourceMgr: sourceMgr))
578 << "' is ignored, continue.";
579 continue; // if there is a comment on the previous line it should be picked up
580 }
581
582 // There should be no other translation function between comment and declaration.
583 if (LupdatePrivate::trFunctionPresent(text)) {
584 qCDebug(lcClang) << "Found another translation function between comment and "
585 "declaration, break.";
586 break;
587 }
588
589 retrievedRawComments.emplace(position: retrievedRawComments.begin(),
590 args: toQt(str: (*comment)->getRawText(SourceMgr: sourceMgr)));
591 lastDecompLoc = sourceMgr.getDecomposedLoc(Loc: (*comment)->getSourceRange().getBegin()).second;
592 } while (comment != rawComments.begin());
593
594 return retrievedRawComments;
595}
596
597/*
598 Read the raw comments and split them according to the prefix.
599 Fill the corresponding variables in the TranslationRelatedStore.
600*/
601void LupdateVisitor::setInfoFromRawComment(const QString &commentString,
602 TranslationRelatedStore *store)
603{
604 const QStringList commentLines = commentString.split(sep: QLatin1Char('\n'));
605
606 static const QRegularExpression
607 cppStyle(
608 QStringLiteral("^\\/\\/(?<identifier>[:=~%]|(\\s*?TRANSLATOR))\\s+(?<comment>.+)$"));
609 static const QRegularExpression
610 cStyleSingle(
611 QStringLiteral("^\\/\\*(?<identifier>[:=~%]|(\\s*?TRANSLATOR))\\s+(?<comment>.+)\\*\\/$"));
612 static const QRegularExpression
613 cStyleMultiBegin(
614 QStringLiteral("^\\/\\*(?<identifier>[:=~%]|(\\s*?TRANSLATOR))\\s+(?<comment>.*)$"));
615
616 static const QRegularExpression isSpace(QStringLiteral("\\s+"));
617 static const QRegularExpression idefix(
618 QStringLiteral("^\\/\\*(?<identifier>[:=~%]|(\\s*?TRANSLATOR))"));
619
620 bool save = false;
621 bool sawStarPrefix = false;
622 bool sourceIdentifier = false;
623
624 int storeLine = store->lupdateLocationLine;
625 int lineExtra = storeLine - 1;
626
627 QString comment, identifier;
628 for (auto line : commentLines) {
629 line = line.trimmed();
630 lineExtra++;
631 if (!sawStarPrefix) {
632 if (line.startsWith(QStringLiteral("//"))) {
633 // Process C++ style comment.
634 save = LupdatePrivate::capture(exp: cppStyle, line, i: &identifier, c: &comment);
635 storeLine = lineExtra;
636 } else if (line.startsWith(s: QLatin1String("/*")) && line.endsWith(s: QLatin1String("*/"))) {
637 // Process C style comment on a single line.
638 storeLine = lineExtra;
639 save = LupdatePrivate::capture(exp: cStyleSingle, line, i: &identifier, c: &comment);
640 } else if (line.startsWith(s: QLatin1String("/*"))) {
641 storeLine = lineExtra;
642 sawStarPrefix = true; // Start processing a multi line C style comment.
643
644 auto result = idefix.match(subject: line);
645 if (!result.hasMatch())
646 continue; // No identifier found.
647 identifier = result.captured(name: QLatin1String("identifier"));
648
649 // The line is not just opening, try grab the comment.
650 if (line.size() > (identifier.size() + 3))
651 LupdatePrivate::capture(exp: cStyleMultiBegin, line, i: &identifier, c: &comment);
652 sourceIdentifier = (identifier == QLatin1String("%"));
653 }
654 } else {
655 if (line.endsWith(s: QLatin1String("*/"))) {
656 sawStarPrefix = false; // Finished processing a multi line C style comment.
657 line = line.remove(s: QLatin1String("*/")).trimmed(); // Still there can be something.
658 }
659
660 if (sourceIdentifier) {
661 line = LupdatePrivate::cleanQuote(s: line.toStdString(),
662 quote: LupdatePrivate::QuoteCompulsary::Left);
663 }
664
665 if (!line.isEmpty() && !comment.isEmpty() && !sourceIdentifier)
666 comment.append(c: QLatin1Char(' '));
667
668 comment += line;
669 save = !sawStarPrefix && !comment.isEmpty();
670 }
671 if (!save)
672 continue;
673
674 // To avoid processing the non TRANSLATOR comments when setInfoFromRawComment in called
675 // from processIsolatedComments
676 if (!store->funcName.isEmpty()) {
677 if (identifier == QStringLiteral(":")) {
678 if (!store->lupdateExtraComment.isEmpty())
679 store->lupdateExtraComment.append(c: QLatin1Char(' '));
680 store->lupdateExtraComment += comment;
681 } else if (identifier == QStringLiteral("=")) {
682 if (!store->lupdateIdMetaData.isEmpty())
683 store->lupdateIdMetaData.append(c: QLatin1Char(' '));
684 store->lupdateIdMetaData = comment; // Only the last one is to be picked up.
685 } else if (identifier == QStringLiteral("~")) {
686 auto first = comment.section(re: isSpace, start: 0, end: 0);
687 auto second = comment.mid(position: first.size()).trimmed();
688 if (!second.isEmpty())
689 store->lupdateAllMagicMetaData.insert(key: first, value: second);
690 } else if (identifier == QLatin1String("%")) {
691 store->lupdateSourceWhenId += comment;
692 }
693 } else if (identifier.trimmed() == QStringLiteral("TRANSLATOR")) {
694 // separate the comment in two in order to get the context
695 // then save it as a new entry in m_stores.
696 // reminder: TRANSLATOR comments are isolated comments not linked to any tr call
697 qCDebug(lcClang) << "Comment = " << comment;
698 TranslationRelatedStore newStore;
699 // need a funcName name in order to get handeled in fillTranslator
700 newStore.funcName = QStringLiteral("TRANSLATOR");
701 auto index = comment.indexOf(QStringLiteral(" "));
702 if (index >= 0) {
703 newStore.contextArg = comment.left(n: index).trimmed();
704 newStore.lupdateComment = comment.mid(position: index).trimmed();
705 }
706 newStore.lupdateLocationFile = store->lupdateLocationFile;
707 newStore.lupdateLocationLine = storeLine;
708 newStore.locationCol = 0;
709 newStore.printStore();
710 m_trCalls.emplace_back(args: std::move(newStore));
711 }
712
713 save = false;
714 comment.clear();
715 identifier.clear();
716 }
717}
718
719void LupdateVisitor::processPreprocessorCalls()
720{
721 QString inputFile = toQt(str: m_inputFile);
722 for (const auto &store : m_stores->Preprocessor) {
723 if (store.lupdateInputFile == inputFile)
724 processPreprocessorCall(store);
725 }
726
727 if (m_qDeclareTrMacroAll.size() > 0 || m_noopTranslationMacroAll.size() > 0)
728 m_macro = true;
729}
730
731void LupdateVisitor::processPreprocessorCall(TranslationRelatedStore store)
732{
733 // To get the comments around the macros
734 const std::vector<QString> rawComments = rawCommentsFromSourceLocation(sourceLocation: store
735 .callLocation(sourceManager: m_context->getSourceManager()));
736 // to pick up the raw comments in the files collected from the preprocessing.
737 for (const auto &rawComment : rawComments)
738 setInfoFromRawComment(commentString: rawComment, store: &store);
739
740 // Processing the isolated comments (TRANSLATOR) in the files included in the main input file.
741 if (store.callType.contains(QStringLiteral("InclusionDirective"))) {
742 auto &sourceMgr = m_context->getSourceManager();
743 const clang::FileID file = sourceMgr.getDecomposedLoc(Loc: store.callLocation(sourceManager: sourceMgr)).first;
744 processIsolatedComments(file);
745 return;
746 }
747
748 if (store.isValid()) {
749 if (store.funcName.contains(QStringLiteral("Q_DECLARE_TR_FUNCTIONS")))
750 m_qDeclareTrMacroAll.emplace_back(args: std::move(store));
751 else
752 m_noopTranslationMacroAll.emplace_back(args: std::move(store));
753 store.printStore();
754 }
755}
756
757bool LupdateVisitor::VisitNamedDecl(clang::NamedDecl *namedDeclaration)
758{
759 if (!m_macro)
760 return true;
761 auto fullLocation = m_context->getFullLoc(Loc: namedDeclaration->getBeginLoc());
762 if (!fullLocation.isValid() || !fullLocation.getFileEntry())
763 return true;
764
765 if (!LupdatePrivate::isFileSignificant(filePath: fullLocation.getFileEntry()->getName().str()))
766 return true;
767
768 qCDebug(lcClang) << "NamedDecl Name: " << QString::fromStdString(s: namedDeclaration->getQualifiedNameAsString());
769 qCDebug(lcClang) << "NamedDecl source: " << QString::fromStdString(s: namedDeclaration->getSourceRange().printToString(
770 SM: m_context->getSourceManager()));
771 // Checks if there is a macro located within the range of this NamedDeclaration
772 // in order to find a context for the macro
773 findContextForTranslationStoresFromPP(namedDeclaration);
774 return true;
775}
776
777void LupdateVisitor::findContextForTranslationStoresFromPP(clang::NamedDecl *namedDeclaration)
778{
779 qCDebug(lcClang) << "=================findContextForTranslationStoresFromPP===================";
780 qCDebug(lcClang) << "m_noopTranslationMacroAll " << m_noopTranslationMacroAll.size();
781 qCDebug(lcClang) << "m_qDeclareTrMacroAll " << m_qDeclareTrMacroAll.size();
782 clang::SourceManager &sm = m_context->getSourceManager();
783
784 // Looking for NOOP context only in the input file
785 // because we are not interested in the NOOP from all related file
786 // Once QT_TR_NOOP are gone this step can be removes because the only
787 // QT_...NOOP left will have an context as argument
788 for (TranslationRelatedStore &store : m_noopTranslationMacroAll) {
789 if (!store.contextArg.isEmpty())
790 continue;
791 clang::SourceLocation sourceLoc = store.callLocation(sourceManager: sm);
792 if (!sourceLoc.isValid())
793 continue;
794 if (LupdatePrivate::isPointWithin(sourceRange: namedDeclaration->getSourceRange(), point: sourceLoc, sm)) {
795
796 store.contextRetrieved = LupdatePrivate::contextForNoopMacro(namedDecl: namedDeclaration, sm);
797 qCDebug(lcClang) << "------------------------------------------NOOP Macro in range ---";
798 qCDebug(lcClang) << "Range " << QString::fromStdString(s: namedDeclaration->getSourceRange().printToString(SM: sm));
799 qCDebug(lcClang) << "Point " << QString::fromStdString(s: sourceLoc.printToString(SM: sm));
800 qCDebug(lcClang) << "=========== Visit Named Declaration =============================";
801 qCDebug(lcClang) << " Declaration Location " <<
802 QString::fromStdString(s: namedDeclaration->getSourceRange().printToString(SM: sm));
803 qCDebug(lcClang) << " Macro Location "
804 << QString::fromStdString(s: sourceLoc.printToString(SM: sm));
805 qCDebug(lcClang) << " Context namedDeclaration->getQualifiedNameAsString() "
806 << QString::fromStdString(s: namedDeclaration->getQualifiedNameAsString());
807 qCDebug(lcClang) << " Context LupdatePrivate::contextForNoopMacro "
808 << store.contextRetrieved;
809 qCDebug(lcClang) << " Context Retrieved " << store.contextRetrieved;
810 qCDebug(lcClang) << "=================================================================";
811 store.printStore();
812 }
813 }
814
815 for (TranslationRelatedStore &store : m_qDeclareTrMacroAll) {
816 clang::SourceLocation sourceLoc = store.callLocation(sourceManager: sm);
817 if (!sourceLoc.isValid())
818 continue;
819 if (LupdatePrivate::isPointWithin(sourceRange: namedDeclaration->getSourceRange(), point: sourceLoc, sm)) {
820 store.contextRetrieved = QString::fromStdString(
821 s: namedDeclaration->getQualifiedNameAsString());
822 qCDebug(lcClang) << "------------------------------------------DECL Macro in range ---";
823 qCDebug(lcClang) << "Range " << QString::fromStdString(s: namedDeclaration->getSourceRange().printToString(SM: sm));
824 qCDebug(lcClang) << "Point " << QString::fromStdString(s: sourceLoc.printToString(SM: sm));
825 qCDebug(lcClang) << "=========== Visit Named Declaration =============================";
826 qCDebug(lcClang) << " Declaration Location " <<
827 QString::fromStdString(s: namedDeclaration->getSourceRange().printToString(SM: sm));
828 qCDebug(lcClang) << " Macro Location "
829 << QString::fromStdString(s: sourceLoc.printToString(SM: sm));
830 qCDebug(lcClang) << " Context namedDeclaration->getQualifiedNameAsString() "
831 << store.contextRetrieved;
832 qCDebug(lcClang) << " Context Retrieved " << store.contextRetrieved;
833 qCDebug(lcClang) << "=================================================================";
834 store.printStore();
835 }
836 }
837}
838
839void LupdateVisitor::generateOutput()
840{
841 qCDebug(lcClang) << "=================generateOutput============================";
842 m_noopTranslationMacroAll.erase(first: std::remove_if(first: m_noopTranslationMacroAll.begin(),
843 last: m_noopTranslationMacroAll.end(), pred: [](const TranslationRelatedStore &store) {
844 // Macros not located in the currently visited file are missing context (and it's normal),
845 // so an output is only generated for macros present in the currently visited file.
846 // If context could not be found, it is warned against in ClangCppParser::collectMessages
847 // (where it is possible to order the warnings and print them consistantly)
848 if (!LupdatePrivate::isFileSignificant(filePath: store.lupdateLocationFile.toStdString()))
849 return true;
850 return false;
851 }), last: m_noopTranslationMacroAll.end());
852
853 m_stores->QNoopTranlsationWithContext.emplace_bulk(values: std::move(m_noopTranslationMacroAll));
854
855 m_qDeclareTrMacroAll.erase(first: std::remove_if(first: m_qDeclareTrMacroAll.begin(),
856 last: m_qDeclareTrMacroAll.end(), pred: [](const TranslationRelatedStore &store) {
857 // only fill if a context has been retrieved in the file we're currently visiting
858 return store.contextRetrieved.isEmpty();
859 }), last: m_qDeclareTrMacroAll.end());
860 m_stores->QDeclareTrWithContext.emplace_bulk(values: std::move(m_qDeclareTrMacroAll));
861
862 processIsolatedComments();
863 m_stores->AST.emplace_bulk(values: std::move(m_trCalls));
864}
865
866QT_END_NAMESPACE
867

source code of qttools/src/linguist/lupdate/clangtoolastreader.cpp