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
clangtoolastreader.cpp
Go to the documentation of this file.
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
6#include "translator.h"
7
8#include <QLibraryInfo>
9
11
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>(child);
25 if (stringLit) {
26 context = toQt(stringLit->getString());
27 return;
28 }
29 exploreChildrenForFirstStringLiteral(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>(decl);
54 clang::CXXMethodDecl *method = llvm::dyn_cast<clang::CXXMethodDecl>(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(method->getBody(), context);
65 }
66 } else if (accessSpec) {
67 if (!accessSpec->getBeginLoc().isValid())
68 continue;
69 QString location = QString::fromStdString(
70 sm.getSpellingLoc(accessSpec->getBeginLoc()).printToString(sm));
71 qsizetype indexLast = location.lastIndexOf(QLatin1String(":"));
72 qsizetype indexBeforeLast = location.lastIndexOf(QLatin1String(":"), indexLast-1);
73 location.truncate(indexBeforeLast);
74 const QString qtInstallDirPath = QLibraryInfo::path(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(qtInstallDirPath, Qt::CaseInsensitive)) {
79 if (location.endsWith(accessForQDeclareTrFunctions))
80 access_for_qdeclaretrfunction = true;
81 if (location.endsWith(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(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(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>(decl)) {
161 clang::CXXRecordDecl *recordDecl = llvm::dyn_cast<clang::CXXRecordDecl>(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;
194 {
195 llvm::raw_string_ostream tmp(context);
196 func->printQualifiedName(tmp);
197 }
198#else
199 context = func->getQualifiedNameAsString();
200#endif
201 return QString::fromStdString(context.substr(0, context.find("::" + funcName, 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(line);
208 if (!result.hasMatch())
209 return false;
210
211 *i = result.captured(QLatin1String("identifier"));
212 *c = result.captured(QStringLiteral("comment")).trimmed();
213
214 if (*i == QLatin1String("%"))
215 *c = LupdatePrivate::cleanQuote(c->toStdString(), QuoteCompulsary::Left);
216
217 return !c->isEmpty();
218 }
219
220 bool hasQuote(llvm::StringRef source)
221 {
222 return source.contains("\"");
223 }
224
225 bool trFunctionPresent(llvm::StringRef text)
226 {
227 if (text.contains(llvm::StringRef("qtTrId(")))
228 return true;
229 if (text.contains(llvm::StringRef("tr(")))
230 return true;
231 if (text.contains(llvm::StringRef("trUtf8(")))
232 return true;
233 if (text.contains(llvm::StringRef("translate(")))
234 return true;
235 if (text.contains(llvm::StringRef("Q_DECLARE_TR_FUNCTIONS(")))
236 return true;
237 if (text.contains(llvm::StringRef("QT_TR_N_NOOP(")))
238 return true;
239 if (text.contains(llvm::StringRef("QT_TRID_N_NOOP(")))
240 return true;
241 if (text.contains(llvm::StringRef("QT_TRANSLATE_N_NOOP(")))
242 return true;
243 if (text.contains(llvm::StringRef("QT_TRANSLATE_N_NOOP3(")))
244 return true;
245 if (text.contains(llvm::StringRef("QT_TR_NOOP(")))
246 return true;
247 if (text.contains(llvm::StringRef("QT_TRID_NOOP(")))
248 return true;
249 if (text.contains(llvm::StringRef("QT_TRANSLATE_NOOP(")))
250 return true;
251 if (text.contains(llvm::StringRef("QT_TRANSLATE_NOOP3(")))
252 return true;
253 if (text.contains(llvm::StringRef("QT_TR_NOOP_UTF8(")))
254 return true;
255 if (text.contains(llvm::StringRef("QT_TRANSLATE_NOOP_UTF8(")))
256 return true;
257 if (text.contains(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(start, point)
268 && sm.isBeforeInTranslationUnit(point, end));
269 }
270
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.getBeginLoc(), RHS.getBeginLoc());
281 }
282
283 bool operator()(const clang::RawComment *LHS, const clang::RawComment *RHS)
284 {
285 return operator()(*LHS, *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(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(func->getNameInfo().getAsString());
314
315 // Only continue if the function a translation function (TODO: deal with alias function...)
316 switch (trFunctionAliasManager.trFunctionByName(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(callExpression->getBeginLoc());
324 if (fileLoc.isInvalid() || !fileLoc.isFileID())
325 return true;
326 // not using line directive (# line)
327 auto presumedLoc = sm.getPresumedLoc(fileLoc, false);
328 if (presumedLoc.isInvalid())
329 return true;
330 info = { presumedLoc.getLine(), 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(info.Filename))
338 return true;
339
340 qCDebug(lcClang) << "************************** VisitCallExpr ****************";
341 // Retrieving the information needed to fill the lupdate translator.
342 // Function independent retrieve
344 store.callType = QStringLiteral("ASTRead_CallExpr");
345 store.funcName = funcName;
346 store.lupdateLocationFile = QString::fromStdString(info.Filename);
347 store.lupdateLocationLine = info.Line;
348 store.contextRetrieved = LupdatePrivate::contextForFunctionDecl(func, 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(callExpression);
359 for (const auto &rawComment : rawComments) {
360 setInfoFromRawComment(rawComment, &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(i);
370 llvm::raw_string_ostream temp(arguments[i]);
371 arg->printPretty(temp, nullptr, policy);
372 }
373
374 // Function dependent retrieve!
375 switch (trFunctionAliasManager.trFunctionByName(funcName)) {
376 case TrFunctionAliasManager::Function_tr:
377 case TrFunctionAliasManager::Function_trUtf8:
378 if (arguments.size() != 3 || !LupdatePrivate::hasQuote(arguments[0]))
379 return true;
380 store.lupdateSource = LupdatePrivate::cleanQuote(arguments[0]);
381 store.lupdateComment = LupdatePrivate::cleanQuote(arguments[1]);
382 store.lupdatePlural = QString::fromStdString(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(arguments[0])
389 || !LupdatePrivate::hasQuote(arguments[1])) {
390 return true;
391 }
392 store.contextArg = LupdatePrivate::cleanQuote(arguments[0]);
393 store.lupdateSource = LupdatePrivate::cleanQuote(arguments[1]);
394 store.lupdateComment = LupdatePrivate::cleanQuote(arguments[2]);
395 store.lupdatePlural = QString::fromStdString(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(arguments[0]))
403 return true;
404 store.lupdateId = LupdatePrivate::cleanQuote(arguments[0]);
405 store.lupdatePlural = QString::fromStdString(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(std::move(store));
413 return true;
414}
415
416void LupdateVisitor::processIsolatedComments()
417{
418 auto &sourceMgr = m_context->getSourceManager();
419 processIsolatedComments(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
431 const auto commentsInThisFile = m_context->Comments.getCommentsInFile(file);
432 if (!commentsInThisFile)
433 return;
434
435 std::vector<clang::RawComment *> tmp;
436 for (const auto &commentInFile : *commentsInThisFile)
437 tmp.emplace_back(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(sourceMgr.getFilename(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(rawComment->getBeginLoc(), false).getLine();
461 store.lupdateLocationFile = QString::fromStdString(
462 sourceMgr.getPresumedLoc(rawComment->getBeginLoc(), false).getFilename());
463 QString comment = toQt(rawComment->getRawText(sourceMgr));
464 qCDebug(lcClang) << " raw Comment : \n" << comment;
465 setInfoFromRawComment(comment, &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(m_context->getFullLoc(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
492 const clang::FileID file = sourceMgr.getDecomposedLoc(sourceLocation).first;
493 const auto commentsInThisFile = m_context->Comments.getCommentsInFile(file);
494 if (!commentsInThisFile)
495 return {};
496
497 std::vector<clang::RawComment *> tmp;
498 for (const auto &commentInFile : *commentsInThisFile)
499 tmp.emplace_back(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(rawComments.begin(), rawComments.end(),
522 &commentAtDeclarationLocation, 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(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(declLocDecomp.first, &invalid).data();
534 if (invalid) {
535 qCDebug(lcClang).nospace() << "An error occurred fetching the source buffer of file: "
536 << toQt(sourceMgr.getFilename(sourceLocation));
537 return {};
538 }
539
540 std::vector<QString> retrievedRawComments;
541 auto lastDecompLoc = declLocDecomp.second;
542 const auto declLineNum = sourceMgr.getLineNumber(declLocDecomp.first, declLocDecomp.second);
543 do {
544 std::advance(comment, -1);
545
546 // Decompose the end of the comment.
547 std::pair<clang::FileID, unsigned> commentEndDecomp
548 = sourceMgr.getDecomposedLoc((*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((*comment)->getRawText(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(commentEndDecomp.first, 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(";}#@") != 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(",") != 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((*comment)->getRawText(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.
584 qCDebug(lcClang) << "Found another translation function between comment and "
585 "declaration, break.";
586 break;
587 }
588
589 retrievedRawComments.emplace(retrievedRawComments.begin(),
590 toQt((*comment)->getRawText(sourceMgr)));
591 lastDecompLoc = sourceMgr.getDecomposedLoc((*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,
603{
604 const QStringList commentLines = commentString.split(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(cppStyle, line, &identifier, &comment);
635 storeLine = lineExtra;
636 } else if (line.startsWith(QLatin1String("/*")) && line.endsWith(QLatin1String("*/"))) {
637 // Process C style comment on a single line.
638 storeLine = lineExtra;
639 save = LupdatePrivate::capture(cStyleSingle, line, &identifier, &comment);
640 } else if (line.startsWith(QLatin1String("/*"))) {
641 storeLine = lineExtra;
642 sawStarPrefix = true; // Start processing a multi line C style comment.
643
644 auto result = idefix.match(line);
645 if (!result.hasMatch())
646 continue; // No identifier found.
647 identifier = result.captured(QLatin1String("identifier"));
648
649 // The line is not just opening, try grab the comment.
650 if (line.size() > (identifier.size() + 3))
651 LupdatePrivate::capture(cStyleMultiBegin, line, &identifier, &comment);
652 sourceIdentifier = (identifier == QLatin1String("%"));
653 }
654 } else {
655 if (line.endsWith(QLatin1String("*/"))) {
656 sawStarPrefix = false; // Finished processing a multi line C style comment.
657 line = line.remove(QLatin1String("*/")).trimmed(); // Still there can be something.
658 }
659
660 if (sourceIdentifier) {
661 line = LupdatePrivate::cleanQuote(line.toStdString(),
662 LupdatePrivate::QuoteCompulsary::Left);
663 }
664
665 if (!line.isEmpty() && !comment.isEmpty() && !sourceIdentifier)
666 comment.append(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(QLatin1Char(' '));
680 store->lupdateExtraComment += comment;
681 } else if (identifier == QStringLiteral("=")) {
682 if (!store->lupdateIdMetaData.isEmpty())
683 store->lupdateIdMetaData.append(QLatin1Char(' '));
684 store->lupdateIdMetaData = comment; // Only the last one is to be picked up.
685 } else if (identifier == QStringLiteral("~")) {
686 auto first = comment.section(isSpace, 0, 0);
687 auto second = comment.mid(first.size()).trimmed();
688 if (!second.isEmpty())
689 store->lupdateAllMagicMetaData.insert(first, 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(index).trimmed();
704 newStore.lupdateComment = comment.mid(index).trimmed();
705 }
706 newStore.lupdateLocationFile = store->lupdateLocationFile;
707 newStore.lupdateLocationLine = storeLine;
708 newStore.locationCol = 0;
709 newStore.printStore();
710 m_trCalls.emplace_back(std::move(newStore));
711 }
712
713 save = false;
714 comment.clear();
715 identifier.clear();
716 }
717}
718
720{
721 QString inputFile = toQt(m_inputFile);
722 for (const auto &store : m_stores->Preprocessor) {
723 if (store.lupdateInputFile == inputFile)
724 processPreprocessorCall(store);
725 }
726
727 // Processing the isolated comments (TRANSLATOR) in the files included in the main input file.
729 for (const clang::FileEntry *file : m_preprocessor->getIncludedFiles()) {
730 auto &sourceMgr = m_context->getSourceManager();
731
732 clang::StringRef fileNameRealPath = file->tryGetRealPathName();
733 if (!LupdatePrivate::isFileSignificant(fileNameRealPath.str())
734 || fileNameRealPath.str() == m_inputFile)
735 continue;
736
737 auto sourceFile = sourceMgr.getFileManager()
738 .getFile(fileNameRealPath);
739 auto sourceLocation = sourceMgr.translateFileLineCol(sourceFile.get(), 1, 1);
740 const clang::FileID fileId = sourceMgr.getDecomposedLoc(sourceLocation).first;
741 processIsolatedComments(fileId);
742 }
743#endif
744
745 if (m_qDeclareTrMacroAll.size() > 0 || m_noopTranslationMacroAll.size() > 0)
746 m_macro = true;
747}
748
749void LupdateVisitor::processPreprocessorCall(TranslationRelatedStore store)
750{
751 // To get the comments around the macros
752 const std::vector<QString> rawComments = rawCommentsFromSourceLocation(store
753 .callLocation(m_context->getSourceManager()));
754 // to pick up the raw comments in the files collected from the preprocessing.
755 for (const auto &rawComment : rawComments)
756 setInfoFromRawComment(rawComment, &store);
757
758 // Processing the isolated comments (TRANSLATOR) in the files included in the main input file.
760 if (store.callType.contains(QStringLiteral("InclusionDirective"))) {
761 auto &sourceMgr = m_context->getSourceManager();
762 const clang::FileID file = sourceMgr.getDecomposedLoc(store.callLocation(sourceMgr)).first;
763 processIsolatedComments(file);
764 return;
765 }
766#endif
767
768 if (store.isValid()) {
769 if (store.funcName.contains(QStringLiteral("Q_DECLARE_TR_FUNCTIONS")))
770 m_qDeclareTrMacroAll.emplace_back(std::move(store));
771 else
772 m_noopTranslationMacroAll.emplace_back(std::move(store));
773 store.printStore();
774 }
775}
776
777bool LupdateVisitor::VisitNamedDecl(clang::NamedDecl *namedDeclaration)
778{
779 if (!m_macro)
780 return true;
781 auto fullLocation = m_context->getFullLoc(namedDeclaration->getBeginLoc());
782 if (!fullLocation.isValid() || !fullLocation.getFileEntry())
783 return true;
784
786 auto fileEntry = fullLocation.getFileEntryRef();
787 if (fileEntry && !LupdatePrivate::isFileSignificant(fileEntry->getName().str()))
788 return true;
789#else
790 if (!LupdatePrivate::isFileSignificant(fullLocation.getFileEntry()->getName().str()))
791 return true;
792#endif
793
794 qCDebug(lcClang) << "NamedDecl Name: " << QString::fromStdString(namedDeclaration->getQualifiedNameAsString());
795 qCDebug(lcClang) << "NamedDecl source: " << QString::fromStdString(namedDeclaration->getSourceRange().printToString(
796 m_context->getSourceManager()));
797 // Checks if there is a macro located within the range of this NamedDeclaration
798 // in order to find a context for the macro
800 return true;
801}
802
804{
805 qCDebug(lcClang) << "=================findContextForTranslationStoresFromPP===================";
806 qCDebug(lcClang) << "m_noopTranslationMacroAll " << m_noopTranslationMacroAll.size();
807 qCDebug(lcClang) << "m_qDeclareTrMacroAll " << m_qDeclareTrMacroAll.size();
808 clang::SourceManager &sm = m_context->getSourceManager();
809
810 // Looking for NOOP context only in the input file
811 // because we are not interested in the NOOP from all related file
812 // Once QT_TR_NOOP are gone this step can be removes because the only
813 // QT_...NOOP left will have an context as argument
814 for (TranslationRelatedStore &store : m_noopTranslationMacroAll) {
815 if (!store.contextArg.isEmpty())
816 continue;
817 clang::SourceLocation sourceLoc = store.callLocation(sm);
818 if (!sourceLoc.isValid())
819 continue;
820 if (LupdatePrivate::isPointWithin(namedDeclaration->getSourceRange(), sourceLoc, sm)) {
821
822 store.contextRetrieved = LupdatePrivate::contextForNoopMacro(namedDeclaration, sm);
823 qCDebug(lcClang) << "------------------------------------------NOOP Macro in range ---";
824 qCDebug(lcClang) << "Range " << QString::fromStdString(namedDeclaration->getSourceRange().printToString(sm));
825 qCDebug(lcClang) << "Point " << QString::fromStdString(sourceLoc.printToString(sm));
826 qCDebug(lcClang) << "=========== Visit Named Declaration =============================";
827 qCDebug(lcClang) << " Declaration Location " <<
828 QString::fromStdString(namedDeclaration->getSourceRange().printToString(sm));
829 qCDebug(lcClang) << " Macro Location "
830 << QString::fromStdString(sourceLoc.printToString(sm));
831 qCDebug(lcClang) << " Context namedDeclaration->getQualifiedNameAsString() "
832 << QString::fromStdString(namedDeclaration->getQualifiedNameAsString());
833 qCDebug(lcClang) << " Context LupdatePrivate::contextForNoopMacro "
834 << store.contextRetrieved;
835 qCDebug(lcClang) << " Context Retrieved " << store.contextRetrieved;
836 qCDebug(lcClang) << "=================================================================";
837 store.printStore();
838 }
839 }
840
841 for (TranslationRelatedStore &store : m_qDeclareTrMacroAll) {
842 clang::SourceLocation sourceLoc = store.callLocation(sm);
843 if (!sourceLoc.isValid())
844 continue;
845 if (LupdatePrivate::isPointWithin(namedDeclaration->getSourceRange(), sourceLoc, sm)) {
846 store.contextRetrieved = QString::fromStdString(
847 namedDeclaration->getQualifiedNameAsString());
848 qCDebug(lcClang) << "------------------------------------------DECL Macro in range ---";
849 qCDebug(lcClang) << "Range " << QString::fromStdString(namedDeclaration->getSourceRange().printToString(sm));
850 qCDebug(lcClang) << "Point " << QString::fromStdString(sourceLoc.printToString(sm));
851 qCDebug(lcClang) << "=========== Visit Named Declaration =============================";
852 qCDebug(lcClang) << " Declaration Location " <<
853 QString::fromStdString(namedDeclaration->getSourceRange().printToString(sm));
854 qCDebug(lcClang) << " Macro Location "
855 << QString::fromStdString(sourceLoc.printToString(sm));
856 qCDebug(lcClang) << " Context namedDeclaration->getQualifiedNameAsString() "
857 << store.contextRetrieved;
858 qCDebug(lcClang) << " Context Retrieved " << store.contextRetrieved;
859 qCDebug(lcClang) << "=================================================================";
860 store.printStore();
861 }
862 }
863}
864
866{
867 qCDebug(lcClang) << "=================generateOutput============================";
868 m_noopTranslationMacroAll.erase(std::remove_if(m_noopTranslationMacroAll.begin(),
869 m_noopTranslationMacroAll.end(), [](const TranslationRelatedStore &store) {
870 // Macros not located in the currently visited file are missing context (and it's normal),
871 // so an output is only generated for macros present in the currently visited file.
872 // If context could not be found, it is warned against in ClangCppParser::collectMessages
873 // (where it is possible to order the warnings and print them consistantly)
874 if (!LupdatePrivate::isFileSignificant(store.lupdateLocationFile.toStdString()))
875 return true;
876 return false;
877 }), m_noopTranslationMacroAll.end());
878
879 m_stores->QNoopTranlsationWithContext.emplace_bulk(std::move(m_noopTranslationMacroAll));
880
881 m_qDeclareTrMacroAll.erase(std::remove_if(m_qDeclareTrMacroAll.begin(),
882 m_qDeclareTrMacroAll.end(), [](const TranslationRelatedStore &store) {
883 // only fill if a context has been retrieved in the file we're currently visiting
884 return store.contextRetrieved.isEmpty();
885 }), m_qDeclareTrMacroAll.end());
886 m_stores->QDeclareTrWithContext.emplace_bulk(std::move(m_qDeclareTrMacroAll));
887
888 processIsolatedComments();
889 m_stores->AST.emplace_bulk(std::move(m_trCalls));
890}
891
892QT_END_NAMESPACE
bool operator()(const clang::RawComment &LHS, const clang::RawComment &RHS)
BeforeThanCompare(const clang::SourceManager &SM)
bool operator()(const clang::RawComment *LHS, const clang::RawComment *RHS)
bool VisitNamedDecl(clang::NamedDecl *namedDeclaration)
void findContextForTranslationStoresFromPP(clang::NamedDecl *namedDeclaration)
bool VisitCallExpr(clang::CallExpr *callExpression)
#define LUPDATE_CLANG_VERSION
Definition cpp_clang.h:42
#define LUPDATE_CLANG_VERSION_CHECK(major, minor, patch)
Definition cpp_clang.h:41
bool hasQuote(llvm::StringRef source)
void exploreChildrenForFirstStringLiteral(clang::Stmt *stmt, QString &context)
bool trFunctionPresent(llvm::StringRef text)
QString exploreBases(clang::CXXRecordDecl *recordDecl, const clang::SourceManager &sm)
bool isPointWithin(const clang::SourceRange &sourceRange, const clang::SourceLocation &point, const clang::SourceManager &sm)
bool isQObjectOrQDeclareTrFunctionMacroDeclared(clang::CXXRecordDecl *recordDecl, QString &context, const clang::SourceManager &sm)
static bool capture(const QRegularExpression &exp, const QString &line, QString *i, QString *c)
bool isFileSignificant(const std::string &filePath)
QString contextForFunctionDecl(clang::FunctionDecl *func, const std::string &funcName)
QString contextForNoopMacro(clang::NamedDecl *namedDecl, const clang::SourceManager &sm)
QString lookForContext(clang::CXXRecordDecl *recordDecl, const clang::SourceManager &sm)
Combined button and popup list for selecting options.
bool isValid(bool printwarning=false)
Definition cpp_clang.h:69
clang::SourceLocation callLocation(const clang::SourceManager &sourceManager)
Definition cpp_clang.h:163
void printStore() const
Definition cpp_clang.h:179